确定两个整数列表是不是包含公共元素(c ++)

Posted

技术标签:

【中文标题】确定两个整数列表是不是包含公共元素(c ++)【英文标题】: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; 传递它,除非您想对其中之一进行排序。 【参考方案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);    

【讨论】:

这种方法可能效率更低。

以上是关于确定两个整数列表是不是包含公共元素(c ++)的主要内容,如果未能解决你的问题,请参考以下文章

检查两个无序列表是不是相等[重复]

C# - 确定IP地址范围是否包含特定地址

如何检查字符串是不是包含列表的任何元素并获取元素的值?

比较两个迭代器并检查哪些元素被添加,删除或两者之间相同

合并在R中共享元素的列出的向量

同步两个对象列表