【问题标题】:Deciding two integer list contains common elements or not (c++)确定两个整数列表是否包含公共元素(c ++)
【发布时间】:2022-01-16 06:46:19
【问题描述】:

我的 c++ 程序存在运行时问题。程序进行数百万次比较两个整数列表是否包含公共元素。我不需要了解哪些元素是常见的。我写了下面的方法,但看起来效率不高。我需要加快程序。那么,执行此过程的最佳方法是什么,或者 c++ 有什么内置方法可以有效地进行此比较?

bool compareHSAndNewSet(list<int> hs , list<int> newset){
            bool isCommon = false;
            for(int x : hs){
                for(int y : newset){
                    if(x == y){isCommon = true; break;}
                }
                if(isCommon == true) {break;}
            }

            return isCommon;

        }

提示:我现在不知道这可能意味着什么。函数的第一个输入(在代码 hs 中)是有序的。

【问题讨论】:

  • 为什么不对列表进行排序?在大多数情况下,它会加快搜索速度。
  • 函数的第一个输入是排序的。但是我该如何使用呢?
  • 对两个列表进行排序。然后使用标准算法std::set_intersection() 获取另一个容器,其中包含两个列表中的元素。如果set_intersection()产生的容器里面有任何元素,那么原始列表中就有共同的元素。
  • 如果两个列表都已排序,您可以在 O(n+m) 复杂度内完成此操作,并且可以大大缩短运行时间。如果只有一个被排序(第一个),它会有点乏味,但仍然可以在 O(m*logn) 中使用正确的随机访问容器来排序序列。
  • 如果这对性能至关重要,不要使用 std::list。其次,您是按值传递的,这可能意味着您的列表被复制了。通过const&amp; 传递它,除非您想对其中之一进行排序。

标签: c++ list


【解决方案1】:

我会使用std::unordered_map&lt;&gt; 将第一个列表添加到,然后检查第二个列表的每个元素是否存在于地图中。这最终将迭代每个列表一次,在地图上进行长度(第一次)插入和长度(第二次)查找。

std::unordered_map&lt;&gt; 的查找和插入复杂度应该为 O(1),但最坏的情况可能会以 O(n) 结束。 (我相信)。

【讨论】:

    【解决方案2】:

    我对各种策略感到好奇,所以我在下面做了一个简单的基准测试。 但是,我不会尝试对第二个容器进行排序;比较容器内的所有数据并移动它们似乎只是为了在交叉点中找到 一个 元素。

    程序在我的电脑上给出了这些结果(Intel(R) Core(TM) i7-10875H CPU @ 2.30GHz):

    vectors: 1.41164
    vectors (dichotomic): 0.0187354
    lists: 12.0402
    lists (dichotomic): 13.4844
    

    如果我们忽略第一个容器已排序并按顺序迭代其元素,我们可以看到一个更简单的容器(这里是一个向量)与元素的相邻存储如果比内存中分布的多个元素(这里是一个列表)要好得多):比 12.0402 快 1.41164 秒(8.5 加速)。

    但如果我们认为第一个容器已排序(如问题中所述),二分法可以进一步改善这种情况。

    最佳情况(向量的二分法)远优于原始情况(按列表的顺序法):0.0187354 秒超过 12.0402 秒(642 加速)。

    当然,所有这些都取决于许多其他因素(数据集的大小、值的分布......);这只是一个微观基准,特定应用程序的行为可能会有所不同。

    请注意,在问题中,参数是按值传递的;这可能会导致一些不需要的副本(除非在调用站点使用移动操作,但我会发现这种功能不常见)。我改为使用 pass-by-reference-on-const。

    另请注意,列表上的二分法是一种悲观化(迭代器没有随机访问,因此它仍然是线性的,但比最简单的线性方法更复杂)。

    编辑:我原来的代码错误,感谢@bitmask,我改了;它不会改变总体思路。

    /**
      g++ -std=c++17 -o prog_cpp prog_cpp.cpp \
          -pedantic -Wall -Wextra -Wconversion -Wno-sign-conversion \
          -O3 -DNDEBUG -march=native
    **/
    
    #include <list>
    #include <vector>
    #include <algorithm>
    #include <chrono>
    #include <random>
    #include <tuple>
    #include <iostream>
    
    template<typename Container>
    bool
    compareHSAndNewSet(const Container &hs,
                       const Container &newset)
    {
      for(const auto &elem: newset)
      {
        const auto it=std::find(cbegin(hs), cend(hs), elem);
        if(it!=cend(hs))
        {
          return true; // found common element
        }
      }
      return false; // no common element
    }
    
    template<typename Container>
    bool
    compareHSAndNewSet_dichotomic(const Container &hs,
                                  const Container &newset)
    {
      for(const auto &elem: newset)
      {
        if(std::binary_search(cbegin(hs), cend(hs), elem))
        {
          return true; // found common element
        }
      }
      return false; // no common element
    }
    
    std::tuple<std::vector<int>, // hs
               std::vector<int>> // newset
    prepare_vectors()
    {
      static auto rnd_gen=std::default_random_engine {std::random_device{}()};
      constexpr auto sz=10'000;
      auto distr=std::uniform_int_distribution<int>{0, 10*sz};
      auto hs=std::vector<int>{};
      auto newset=std::vector<int>{};
      for(auto i=0; i<sz; ++i)
      {
        hs.emplace_back(distr(rnd_gen));
        newset.emplace_back(distr(rnd_gen));
      }
      std::sort(begin(hs), end(hs));
      return {hs, newset};
    }
    
    std::tuple<std::list<int>, // hs
               std::list<int>> // newset
    prepare_lists(const std::vector<int> &hs,
                  const std::vector<int> &newset)
    {
      return {std::list(cbegin(hs), cend(hs)),
              std::list(cbegin(newset), cend(newset))};
    }
    
    double // seconds (1e-6 precision) since 1970/01/01 00:00:00 UTC
    get_time()
    {
      const auto now=std::chrono::system_clock::now().time_since_epoch();
      const auto us=std::chrono::duration_cast<std::chrono::microseconds>(now);
      return 1e-6*double(us.count());
    }
    
    int
    main()
    {
      constexpr auto generations=100;
      constexpr auto iterations=1'000;
      auto duration_v=0.0;
      auto duration_vd=0.0;
      auto duration_l=0.0;
      auto duration_ld=0.0;
      for(auto g=0; g<generations; ++g)
      {
        const auto [hs_v, newset_v]=prepare_vectors();
        const auto [hs_l, newset_l]=prepare_lists(hs_v, newset_v);
        for(auto i=-1; i<iterations; ++i)
        {
          const auto t0=get_time();
          const auto comp_v=compareHSAndNewSet(hs_v, newset_v);
          const auto t1=get_time();
          const auto comp_vd=compareHSAndNewSet_dichotomic(hs_v, newset_v);
          const auto t2=get_time();
          const auto comp_l=compareHSAndNewSet(hs_l, newset_l);
          const auto t3=get_time();
          const auto comp_ld=compareHSAndNewSet_dichotomic(hs_l, newset_l);
          const auto t4=get_time();
          if((comp_v!=comp_vd)||(comp_v!=comp_l)||(comp_v!=comp_ld))
          {
            std::cerr << "comparison mismatch\n";
          }
          if(i>=0) // first iteration is dry-run (warmup)
          {
            duration_v+=t1-t0;
            duration_vd+=t2-t1;
            duration_l+=t3-t2;
            duration_ld+=t4-t3;
          }
        }
      }
      std::cout << "vectors: " << duration_v << '\n';
      std::cout << "vectors (dichotomic): " << duration_vd << '\n';
      std::cout << "lists: " << duration_l << '\n';
      std::cout << "lists (dichotomic): " << duration_ld << '\n';
      return 0;
    }
    

    【讨论】:

    • 当然。打败我吧! ;) 但是,是的,我自己的类似实验表明,最好的办法不是使用列表,而是使用向量(哦,惊喜)。
    • 啊,但是有一个附录:请注意,在排序 (!) 向量的情况下,您可以使用 std::binary_search 而不是 std::lower_bound,从而为大型数据集提供更好的运行时间。跨度>
    • @prog-fh 非常感谢您的帮助!它加快了我的进程:)
    • @bitmask 谢谢,lower_bound()的使用出错了!
    • @prog-fh 不,我很确定你必须在列表迭代器的情况下进行线性搜索。
    【解决方案3】:

    您可以尝试对列表进行排序并使用set_intersection

    bool compareHSAndNewSet(list<int> hs , list<int> newset){
        hs.sort();
        newset.sort();
        list<int>::iterator i;
        list<int> commonElts (hs.size()+newset.size());
        i = std::set_intersection(hs.begin(), hs.end(), newset.begin(), newset.end(), commonElts.begin());
        commonElts.resize(i - commonElts.begin());
        return (v.size() == 0);    
    

    【讨论】:

    • 这种方法可能效率更低。
    猜你喜欢
    • 1970-01-01
    • 2010-12-13
    • 2021-12-19
    • 2012-09-28
    • 1970-01-01
    • 1970-01-01
    • 2023-01-13
    • 2012-12-07
    • 2020-02-07
    相关资源
    最近更新 更多