我对各种策略感到好奇,所以我在下面做了一个简单的基准测试。
但是,我不会尝试对第二个容器进行排序;比较容器内的所有数据并移动它们似乎只是为了在交叉点中找到 一个 元素。
程序在我的电脑上给出了这些结果(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;
}