STL矢量元素去除效率

STL vector element removal efficiency

本文关键字:效率 元素 STL      更新时间:2023-10-16

我有一个对象向量。所有对象元素都是唯一的。为类的唯一id定义函子。

从vector

中删除object元素会更有效吗?
myvec.erase(std::remove_if(myvec.begin(), myvec.end(), MyClass(id)), myvec.end());
//OR
itonmyvec = std::find_if(myvec.begin(), myvec.end(), MyClass(id));
if(itonmyvec != myvec.end())
    myvec.erase(itonmyvec);

实际最快的方法两者都不是。两者都受到标准中附加的约束,即保持剩余元素的相对顺序,但这不是您所要求的。

因此,最快的解决方案是
  1. 查找元素
  2. 与最后一个元素交换
  3. 删除新的最后一个元素。

执行O(1)个赋值,而不是O(N)个。

由于您使用的是矢量,因此两种建议的实现几乎是相同的。

第一个将遍历vector一次,同时测试每个元素并压缩vector,然后最后析构最后一个元素并缩短vector。

第二个操作遍历vector对象,直到找到该元素,然后遍历vector对象的其余部分进行压缩并最终缩短vector对象。

事实证明,两个版本之间唯一真正的区别是,第二个版本将能够平均节省O(n/2)个比较。如果比较便宜,性能将更多地取决于系统的其他属性,例如,哪个实现对缓存更友好。

因此,最后的答案只能是基准测试吧!

注::第一个版本必须检查remove_if的结果是否与向量的end完全相等,以便在两个版本中以相同的方式进行检查(或不进行检查)

Docs for remove_if say

从范围中删除所有满足特定条件的元素…

当find_if被指定为 时

返回范围内的第一个元素…

既然知道vector中的所有元素都是唯一的,那么可以在第一次匹配之后安全地停止搜索。这意味着对于这种特定情况,第二种形式可能更快。

还请注意,如注释中所述,第一种方法是错误的。remove_if返回一个迭代器到(可能更新的)范围的末端。你不希望从你的矢量中删除它——它保证不匹配你的目标值。

对于MSalters给出的答案,我已经写下了代码。我无法将代码添加到答案的注释中,所以添加到新答案中。希望这对你有帮助!

//Remove 3rd element
vector<int> v {1,2,3,4,5};          //C++11 compiler needed!
vector<int>::iterator it1 = find(v.begin(), v.end(), 3); 
vector<int>::iterator it2 = --v.end();
std::swap(*it1, *it2);
v.pop_back();

如果您愿意牺牲异常安全性,那么您可以比当前接受的答案做得更好:而不是与最后一个元素交换然后调用pop_back(),您可以简单地用最后一个元素覆盖要删除的元素。你必须向后迭代,从最后一个元素开始。

如果您使用的是整数,那么这很好,而且很可能节省大量时间。如果你使用的是谁也不知道会抛出移动拷贝的类,那么你就有麻烦了,你的异常安全保证也就完了。

下面是一个示例代码:
#include <iostream>
#include <vector>
template <typename Vector, typename Pred>
void remove(Vector& v, Pred p) {
  auto last(v.rbegin());
  for (auto pos(last), rend(v.rend()); pos!=rend; ++pos) {
    if (p(*pos))
      // overwrite the element to be removed with the currently last element
      *pos = std::move(*last++);
  }
  v.erase(last.base(), v.end());  
}
// pretty print the vector for debugging
template <typename Vector>
void show(const Vector& v) {
  std::cout << "{ ";
  auto pos(v.begin());
  auto last(v.end());
  if (pos!=last) {
    --last;
    while (pos!=last)
      std::cout << *pos++ << ", ";
    std::cout << *last;
  }
  std::cout << " }" << std::endl;
}
// a simple-minded test that removes all 2s from the vector
void remove_2s(std::initializer_list<int> l) {
  auto equals_two = [](int i) { return i==2;};
  std::vector<int> v{l};
  remove(v, equals_two);
  show(v);
}
int main() {
  remove_2s({1, 2, 3, 2, 5, 7, 4, 2, 6});
  remove_2s({ 1, 2 });
  remove_2s({ 2, 1 });  
  remove_2s({ 2 });
  remove_2s({ 2, 2 });  
  remove_2s({  });
  remove_2s({ 1 });
}