C++ C++;
我有一组C++ C++;,c++,algorithm,stl,set-intersection,C++,Algorithm,Stl,Set Intersection,我有一组std::set。我想以最快的方式找到集合中所有集合的交集。集合中的集合数通常非常小(~5-10),每个集合中的元素数通常少于1000个,但有时可能高达10000个左右。但是我需要以尽可能快的速度进行成千上万次的交叉。我尝试对以下几种方法进行基准测试: 初始复制第一个集合的std::set对象中的就地交点。然后,对于后续的集合,它迭代自身的所有元素和集合的第i个集合,并根据需要从自身移除项 使用std::set_intersection到临时std::set中,将内容交换到当前集,然后再
std::set
。我想以最快的方式找到集合中所有集合的交集。集合中的集合数通常非常小(~5-10),每个集合中的元素数通常少于1000个,但有时可能高达10000个左右。但是我需要以尽可能快的速度进行成千上万次的交叉。我尝试对以下几种方法进行基准测试:
std::set
对象中的就地交点。然后,对于后续的集合,它迭代自身的所有元素和集合的第i个集合,并根据需要从自身移除项std::set_intersection
到临时std::set
中,将内容交换到当前集,然后再次找到当前集与下一集的交集并插入临时集,依此类推向量
作为目标容器,而不是std::set
std::list
而不是向量
,怀疑列表
将提供从中间更快的删除std::unordered_set
)并检查所有集中的所有项事实证明,当每个集合中的元素数较少时,使用
向量
稍微快一点,而对于较大的集合,列表
稍微快一点。就地使用set
比两者都慢,其次是set\u交集
和散列集。是否有更快的算法/数据结构/技巧来实现这一点?如果需要,我可以发布代码片段。谢谢 您可能希望尝试对std::set_intersection()进行泛化:算法是对所有集合使用迭代器:
end()
,则完成。因此,可以假设所有迭代器都是有效的x
x
一样大的第一个元素x
则将其作为新的候选值,并按迭代器顺序再次搜索x
上,那个么您找到了交集的一个元素:记录它,递增所有迭代器,重新开始Night是个很好的顾问,我想我可能有个主意;)
- 如今,内存比CPU慢得多,如果所有数据都放在一级缓存中没有什么大不了的,但它很容易溢出到二级缓存或三级缓存:5组1000个元素已经是5000个元素,这意味着5000个节点,一组节点至少包含3个指针+对象(即,32位机器上至少包含16个字节,64位机器上至少包含32个字节)=>这至少是80k内存,最近的CPU只有32k用于L1D,因此我们已经扩展到L2
- 前面的事实由于集合节点可能分散在内存中而不是紧密地打包在一起的问题而变得更加复杂,这意味着缓存线的一部分充满了完全不相关的内容。这可以通过提供一个使节点彼此靠近的分配器来缓解
- CPU在顺序读取(它们可以在您需要内存之前预取内存,这样您就不用等待内存)方面比随机读取要好得多,这一事实进一步加剧了这种情况(不幸的是,树结构会导致非常随机的读取)
向量
作为我们的中介结构;尽管需要注意的是,只能从末端插入/删除,以避免重新定位
所以我想到了一个非常简单的方法:
#include <cassert>
#include <algorithm>
#include <set>
#include <vector>
// Do not call this method if you have a single set...
// And the pointers better not be null either!
std::vector<int> intersect(std::vector< std::set<int> const* > const& sets) {
for (auto s: sets) { assert(s && "I said no null pointer"); }
std::vector<int> result; // only return this one, for NRVO to kick in
// 0. Check obvious cases
if (sets.empty()) { return result; }
if (sets.size() == 1) {
result.assign(sets.front()->begin(), sets.front()->end());
return result;
}
// 1. Merge first two sets in the result
std::set_intersection(sets[0]->begin(), sets[0]->end(),
sets[1]->begin(), sets[1]->end(),
std::back_inserter(result));
if (sets.size() == 2) { return result; }
// 2. Merge consecutive sets with result into buffer, then swap them around
// so that the "result" is always in result at the end of the loop.
std::vector<int> buffer; // outside the loop so that we reuse its memory
for (size_t i = 2; i < sets.size(); ++i) {
buffer.clear();
std::set_intersection(result.begin(), result.end(),
sets[i]->begin(), sets[i]->end(),
std::back_inserter(buffer));
swap(result, buffer);
}
return result;
}
#包括
#显然,我不能保证它的速度。问题实际上取决于你是否需要找到许多共同的元素,因为这会改变你能想到的“最佳”结构。例如,第六种方法可以是简单地使用andstd::unordered_map
并计算每个元素的出现次数。元素总数是O(N)。然后,您只需选取总数等于集合数的元素,即不同元素数中的O(M)。“不知道它的表现会有多好。”马蒂厄姆说。我懂了。我将尝试一下,尽管我怀疑,由于散列和其他开销,它不会比std::list快。谢谢@马蒂厄姆。此方法将以未排序的顺序给出结果集。幸运的是,我有两个用例,一个需要按顺序排序的结果,另一个不需要。如果这个方法相当快,我至少可以在交叉点不需要排序的情况下使用它。@MatthieuM。我尝试了这种方法,对于我的数据,这只比我的方法5(使用unordered\u set
)稍微快一点。你可以试试。最坏情况下的线性(如果集合的元素基本相同,则无法避免这种情况),但如果交集很小,则速度会快得多。毕竟,当使用std::set
时,我不建议使用std::find_if
,std::set
功能的std::lower_bound
和std::upper_bound
通常更快。@MatthieuM。不是在这种情况下,find_if
平均不需要推进两个以上的元素,因此是O(1),而