在C++中,从已排序的整数向量中搜索和删除元素的最快方法
Fastest method of searching and removing elements from a sorted vector of integers in C++
我有一个排序整数的大向量。我需要快速查找并从数组中删除八个值。
例如,向量a包括元素
{1, 4, 7, 15, 16, 19, 24, 26, 31, 53, 67, 68, 73, 75, 77, 82}
向量b包括八个值
{4, 15, 19, 24, 67, 68, 73, 75}
完成操作后,矢量a现在应该具有
{1, 7, 16, 26, 31, 53, 77, 82}
我以前的解决方案很慢:
for (vector<int>::iterator val = b.begin(); val != b.end(); val++)
a.erase(remove(a.begin(), a.end(), *val), a.end());
有没有更快的方法?
编辑:
实际上,我的"A"向量比我的"B"向量大很多。也许通过二进制搜索来搜索单个元素并删除它们更好?
第2版:
也许向量不是这种操作的好容器。我认为我不能使用forward_list,因为我不能用C++11进行编译。也许我可以使用不同的容器,然后将结果复制到向量中?
我可能会做这样的事情:
std::vector<int> temp;
std::set_difference(a.begin(), a.end(),
b.begin(), b.end(),
std::back_inserter(temp));
std::swap(a, temp);
根据编辑的问题进行编辑:
假设你的a
向量比b
向量大得多,我会考虑第二个问题:完成后,你需要a
保持排序吗?
如果允许重新排列a
中的元素,那么您可以大幅提高速度:您可以将要删除的项目与a
的最后一个元素交换,然后从末尾删除它(它具有恒定的复杂性),而不是执行删除/擦除操作来从a
的中间删除项目。这使得删除量恒定,因此总体复杂度为O(N log M)(其中N=b.size(),M=a.size())
如果你必须保持秩序,你仍然可以在一定程度上提高速度:不是从a
中删除元素,然后立即擦除删除的元素,而是执行std::remove_if
以找到a
中需要删除的所有元素,然后执行一次擦除以删除所有这些元素。
现在,对于您移除的a
的每个元素,都使用对remove
的单独调用。对于每个remove
,复制(或移动,如果适用)每个移除点之后的a
的所有元素。这意味着,如果从a
中删除10个元素,则(平均)将a
的一半复制10次。通过使用单个remove_if
,可以只复制a
的每个元素一次。
不幸的是,remove_if
并不能为您提供一个利用b
中的订购的好方法。你可以使用二进制搜索,这对一些人有帮助,但没有你想要的那么多。
如果你不介意写自己的循环,你可以充分利用a
和b
都是排序的,比如
#include <vector>
#include <iostream>
// Compute the difference between two "set"s in-place. Each 'set' must be a
// sorted sequence.
//
template <class FwdIt, class InIt>
FwdIt
inplace_set_difference(FwdIt b1, FwdIt e1, InIt b2, InIt e2) {
FwdIt pos = b1;
while (pos != e1 && b2 != e2) {
if (*pos < *b2)
*b1++ = *pos++;
else if (*b2 < *pos)
++b2;
else
++pos;
}
while (pos != e1)
*b1++ = *pos++;
return b1;
}
int main() {
std::vector<int> a{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
std::vector<int> b{ 2, 5, 9 };
auto it = inplace_set_difference(a.begin(), a.end(), b.begin(), b.end());
a.erase(it, a.end());
for (auto i : a)
std::cout << i << 't';
}
如果你想让它最大限度地通用,你可能想把它改为只在迭代器上使用后增量,允许用户传递比较运算符,而不是直接使用<
,等等。这些都是可怕的"读者练习"。
可能值得注意的是,这与set_difference
通常使用的算法基本相同,只是进行了细微的调整即可操作。不过,这确实会导致接口发生实质性变化:因为它可以(而且确实)在集合中创建重复元素,所以它可以应用于排序序列(向量、deque、hash等),但不能应用于关联容器([unordered_][multi_](set|map)
)。
由于它分别正好遍历a
和b
一次,所以它显然是O(N + M)
,但由于我们从M
(=b.size())小的想法开始,它实际上是O(N)
注意:测试代码大量使用C++11特性(例如,初始化两个向量),但我认为算法实现本身在C++98/03中应该很好。
与其删除元素,不如将项添加到新向量中。假设您的输入是"original"(A)和"toRemove"(B),只需为original和toRemove:创建迭代器
如果原始中的下一个项目与toRemove中的下个项目匹配,则将其删除。否则,将其复制到结果中。如果它大于toRemove中的下一个项目,请转到toRemove的下一项目,然后再次运行比较。
通过这种方式,您只对每个列表迭代一次,而不是在"remove"操作期间不断复制数组值。
此解决方案将在O(A+B)中运行,这比您当前(和建议的)解决方案更快。
出于比较目的:您现有的解决方案大致为O(A*A*B)(A表示删除,A表示非优化搜索,B表示在B上迭代)
您的编辑建议进行二进制搜索以删除元素;这只会将您的原始解决方案改进为O(logA*A*B)(A用于删除,logA用于优化搜索,B用于在B上迭代)。
矢量头中的内置函数速度更快。例如,
向量vect;
在添加所有元素后,您可以使用
排序(vect.begin(),vect.end());
这将按升序对列表进行排序。对于下降,您可能需要执行
reverse(vect.begin,vect.end());
排序列表上。
- 如何从存储在std::映射中的std::集中删除元素
- 从嵌套在std::映射中的std::列表中删除元素的最佳方式
- 从矢量中删除元素后出现隔离错误
- 如何使用remove_if从矢量中删除元素
- 如何从使用 for 循环中的矢量大小的矢量中删除元素
- C++映射不删除元素
- 如何从标准::元组中删除元素?
- 在动态数组中添加/删除C++元素
- 如何通过比较彼此的成员从QStringList中删除元素
- 如何修复从矢量中删除元素的错误?
- 从 std::set 中删除元素,同时在 C++17 中迭代该元素
- 使用remove_if从矢量中删除元素
- 用C++从三维矢量中删除元素
- 从对象数组中删除元素
- 如何在不使用 vector::erase() 的情况下编写自定义 Vector 方法来删除元素?
- C++ - 按自定义数据类型向量的值删除元素
- 从任意容器中廉价删除元素的惯用方法?
- 从数组中删除元素不起作用的函数
- 删除元素 BST
- 从向量中删除元素时未处理的异常