【发布时间】:2012-12-11 10:33:01
【问题描述】:
最近(来自一个 SO 评论)我了解到 std::remove 和 std:remove_if 是稳定的。我是否错误地认为这是一个糟糕的设计选择,因为它会阻止某些优化?
想象一下删除 1M std::vector 的第一个和第五个元素。因为稳定性,我们不能用交换实现remove。相反,我们必须移动所有剩余的元素。 :(
如果我们不受稳定性的限制,我们实际上可以(对于 RA 和 BD 迭代器)有 2 个迭代器,一个从前面,第二个从后面,然后使用交换来结束要移除的项目。我相信聪明的人可能会做得更好。我的问题是一般性的,而不是我正在谈论的特定优化。
编辑:请注意,C++ 宣传零开销原则,还有std::sort 和std::stable_sort 排序算法。
EDIT2: 优化将类似于以下内容:
对于remove_if:
- bad_iter 从头开始查找谓词返回 true 的元素。
- good_iter 从末尾查找谓词返回 false 的那些元素。
当双方都找到了预期的东西时,他们交换了他们的元素。终止地址为good_iter <= bad_iter。
如果有帮助,可以将其视为快速排序算法中的一个迭代器,但我们不会将它们与特殊元素进行比较,而是使用上述谓词。
EDIT3:我玩弄并试图找到最坏的情况(remove_if 的最坏情况 - 请注意谓词很少为真),我得到了这个:
#include <vector>
#include <string>
#include <iostream>
#include <map>
#include <algorithm>
#include <cassert>
#include <chrono>
#include <memory>
using namespace std;
int main()
{
vector<string> vsp;
int n;
cin >> n;
for (int i =0; i < n; ++i)
{ string s = "123456";
s.push_back('a' + (rand() %26));
vsp.push_back(s);
}
auto vsp2 = vsp;
auto remove_start = std::chrono::high_resolution_clock::now();
auto it=remove_if(begin(vsp),end(vsp), [](const string& s){ return s < "123456b";});
vsp.erase(it,vsp.end());
cout << vsp.size() << endl;
auto remove_end = std::chrono::high_resolution_clock::now();
cout << "erase-remove: " << chrono::duration_cast<std::chrono::milliseconds>(remove_end-remove_start).count() << " milliseconds\n";
auto partition_start = std::chrono::high_resolution_clock::now();
auto it2=partition(begin(vsp2),end(vsp2), [](const string& s){ return s >= "123456b";});
vsp2.erase(it2,vsp2.end());
cout << vsp2.size() << endl;
auto partition_end = std::chrono::high_resolution_clock::now();
cout << "partition-remove: " << chrono::duration_cast<std::chrono::milliseconds>(partition_end-partition_start).count() << " milliseconds\n";
}
C:\STL\MinGW>g++ test_int.cpp -O2 && a.exe
12345678
11870995
erase-remove: 1426 milliseconds
11870995
partition-remove: 658 milliseconds
对于其他用途,分区更快,相同或更慢。让我不解。 :D
【问题讨论】:
-
我不认为这是失败的。软件设计就是权衡取舍。如果标准算法不能满足您的某些要求,那么没有什么能阻止您推出自己的算法。
-
“由于稳定性,我们不能通过交换实现删除,而是必须移动每个剩余的元素”。如果向量已排序,并且交换违反了该不变量,该怎么办?此外,如果您需要此类优化,您可以随时根据您的软件和设计要求实施自己的移除。
-
@NoSenseEtAl:或者您可以将您的算法称为
unordered_remove()或类似名称。我认为您提出的算法足够有用,可以包含在标准库中,但现有算法也是如此。哪个是“规范”名称remove()的问题是政治问题。 -
@NoSenseEtAl:您在第二次编辑中的提案已经在标准中,它被称为
std::partition。从remove_if的 POV 来看,它不必要地保留了每个已删除值的副本,因此效率低下。但是你可以调整它以从好的元素转移到坏的元素,而不是交换。 -
为什么关闭?他不是在哭“哇哇,我希望我的不稳定从邪恶标准委员会中移除”,而是问一个关于 C++ 标准库设计的有效问题。即使您不同意他的推理(这就是答案的目的),我也看不出为什么这不是建设性的。
标签: c++ stl complexity-theory