确定两个整数列表是不是包含公共元素(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&
传递它,除非您想对其中之一进行排序。
【参考方案1】:
我会使用std::unordered_map<>
将第一个列表添加到,然后检查第二个列表的每个元素是否存在于地图中。这最终将迭代每个列表一次,在地图上进行长度(第一次)插入和长度(第二次)查找。
std::unordered_map<>
的查找和插入复杂度应该为 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 ++)的主要内容,如果未能解决你的问题,请参考以下文章