我假设你所说的“最好”是指“最快且有效”的算法。由于这是一个关于效率的问题,我进行了简单的基准测试,比较了几种算法的效率。请注意,它们有些不同,因为问题有点未明确 - 引起的问题(和用于基准测试的假设)是:
- 是否保证
vDestination
包含来自vSource
的所有元素?(假设:否)
- 在
vDestination
或vSource
中允许重复吗?(假设:是,在两个向量中都是如此)
- 结果向量中元素的顺序是否重要?(测试了两种情况的算法)
- 如果
vDestination
中的任何元素等于vSource
中的任何元素,则是否应删除vDestination
中的每个元素,还是一对一?(假设:是,在两个向量中都是如此)
vDestination
和vSource
的大小是否受到限制?它们中的一个总是更大或者非常大吗?(测试了几种情况)
- 在注释中已经解释了向量不需要排序,但是我包括了这一点,因为从问题中不容易看出(假设两个向量都不排序)
如您所见,算法将在几个点上有所不同,因此,可以猜测最佳算法取决于您的用例。比较的算法包括:
- 原始算法(提出问题)- 基线
- @dkg答案中提出的算法
- @Revolver_Ocelot答案中提出的算法+额外的排序(算法所需)和预留空间给结果向量
- @Jarod42答案中提出的算法
- 基于集合的算法(下面介绍 - 主要是@Jarod42算法的优化)
- 计数算法(下面介绍)
基于集合的算法:
std::unordered_set<int> elems(vSource.begin(), vSource.end());
auto i = destination.begin();
auto target = destination.end();
while(i <= target) {
if(elems.count(*i) > 0)
std::swap(*i, *(--target));
else
i++;
}
destination.erase(target, destination.end());
计数算法:
std::unordered_map<int, int> counts;
counts.max_load_factor(0.3);
counts.reserve(destination.size());
for(auto v: destination) {
counts[v]++;
}
for(auto v: source) {
counts[v]--;
}
auto i = destination.begin();
for(auto k: counts) {
if(k.second < 1) continue;
i = std::fill_n(i, k.second, k.first);
}
destination.resize(std::distance(destination.begin(), i));
使用
Celero库执行基准测试过程如下:
- 生成
n
个伪随机int
(其中n
在集合{10,100,1000,10000,20000, 200000}
中),并将它们放到一个vector
中。
- 将其中的一部分整数(数量占比为
m
)复制到第二个vector
(占比集合为{0.01, 0.1, 0.2, 0.4, 0.6, 0.8}
,至少有一个元素)。
- 启动计时器。
- 执行删除程序。
- 停止计时器。
由于其余算法所需时间太长,我只对包含超过10,000个元素的数据集执行了算法3、5和6。如果你愿意,可以自己进行测试。
简而言之:如果你的向量包含少于1000个元素,则选择任何你喜欢的方法。如果它们更长,请依赖于
vSource
的大小。如果它小于
vDestination
的50%,则选择基于集合的算法;如果它大于50%,则对它们进行排序并选择@Revolver_Ocelot的解决方案(它们在约60%上并列,基于集合的算法对于
vSource
为
vDestination
大小的1%时速度可提高2倍)。请不要依赖顺序或提供从一开始就排序的向量——要求保持顺序会严重减慢处理速度。在你的用例,编译器、标志和硬件上进行基准测试。我附上了我的基准测试链接,以便你复制它们。
完整结果(文件
vector-benchmarks.csv
)可以在GitHub上与基准测试代码(文件
tests/benchmarks/vectorRemoval.cpp
)一起查看
here。
请记住这些是我在我的计算机,我的编译器等等中获得的结果——在你的情况下,它们会有所不同(特别是当涉及到一个算法比另一个更好的点时)。我使用的是Fedora 24上的GCC 6.1.1和
-O3
。
vDestination
转换为std::list
(或智能指针列表?),以避免在std::vector
中进行昂贵的删除操作(因为它必须连续),最后再转回std::vector
。 - slawekwinunordered_set
(来自vSource
)是否有助于提高效率,这可能值得一试。理论上应该将最坏情况从N * M
改为N + M
(假设N
是vDestination
的大小,M
是vSource
的大小-每个vDestination
元素只需查找一次,而不是多达M
次查找+集合创建),但这取决于桶配置。另外,您没有提到重复项会发生什么-应该删除一次还是全部删除? - Tomasz Lewowski