如何从排序的矢量中有效地清除值?
假设vec
是可移动和可复制对象的排序向量。删除所有匹配value
的元素的最有效方法是什么?如何从排序的矢量中有效地清除值?
这是正确和最有效的方法吗?
auto lb = std::lower_bound(vec.begin(), vec.end(), value);
vec.erase(lb, std::upper_bound(std::next(lb), vec.end(), value));
什么是复杂性? (考虑到擦除后需要的任何移动)。
我已经做了从排序容器擦除四种不同的方法一些简要的测试。
void erase_v1(std::vector<int> &vec, int value)
{
vec.erase(std::remove(std::begin(vec), std::end(vec), value), std::end(vec));
}
void erase_v2(std::vector<int> &vec, int value)
{
auto lb = std::lower_bound(std::begin(vec), std::end(vec), value);
if (lb != std::end(vec) && *lb == value) {
auto ub = std::upper_bound(lb, std::end(vec), value);
vec.erase(lb, ub);
}
}
void erase_v3(std::vector<int> &vec, int value)
{
auto pr = std::equal_range(std::begin(vec), std::end(vec), value);
vec.erase(pr.first, pr.second);
}
// Surt's code, doesn't preserve sorted order
void erase_v4(std::vector<int> &vec, int value)
{
// get the range in 2*log2(N), N=vec.size()
auto bounds = std::equal_range(vec.begin(), vec.end(), value);
// calculate the index of the first to be deleted O(1)
auto last = vec.end() - std::distance(bounds.first, bounds.second);
// swap the 2 ranges O(equals) , equal = std::distance(bounds.first, bounds.last)
std::swap_ranges(bounds.first, bounds.second, last);
// erase the victims O(equals)
vec.erase(last, vec.end());
}
测试用千万的std::vector
一个元素,在范围[0..9]
填充有随机数,然后排序(MS的Visual C++ 2013)。
擦除值0
(容器的前部),有代表性的时间是这样的:
time=14.3894 size=8999147 // v1, milliseconds and updated container size
time=11.9486 size=8999147 // v2
time=11.5548 size=8999147 // v3
time=1.78913 size=8999147 // v4 (Surt)
擦除5
(容器的中间):
time=12.8223 size=9000844
time=4.89388 size=9000844
time=4.87589 size=9000844
time=1.77284 size=9000844
擦除9
(端容器):
time=12.64 size=9000820
time=0.00373372 size=9000820
time=0.00339429 size=9000820
time=1.29899 size=9000820
Erase 13
(值不会在容器):
time=11.8641 size=10000000
time=0.002376 size=10000000
time=0.00203657 size=10000000
time=0.00220628 size=10000000
的erase/remove
方法总是在整个容器进行迭代,并且是越慢,lower_bound/upper_bound
和equal_range
方法是在多次运行几乎相同。我更喜欢最新的版本,因为它是正确的,更简单的代码,并减少打字。
编辑:定时Surt's code的要求。它始终是以不保存排序顺序为代价的快速。
出色的工作! – 2014-11-03 18:16:30
@Blastfurnace,我是否也可以让你解决我的问题,我想知道答案以备将来参考。 – Surt 2014-11-03 19:23:13
@Surt:完成并更新。 – Blastfurnace 2014-11-03 19:49:02
这在value
实际上并未出现在vec
中的情况下不正确。因此,在最起码你必须做的:
auto lb = std::lower_bound(vec.begin(), vec.end(), value);
if (lb != vec.end() && *lb == value) {
vec.erase(lb, std::upper_bound(std::next(lb), vec.end(), value));
}
至于最有效的问题:我相信,在一般情况下,什么都不知道会发生什么是在vec
,是的。复杂性仍然是O(N)
,因为erase()
是O(N)
- 如果您像第二个元素一样擦除,则无法真正进行非线性擦除。但是在寻找擦除范围方面,O(log N)
就像它得到的那样好,而且你得到了它。
upper_bound()
或只是find_if()
对第二部分更好的问题完全取决于您是否有可能拥有大量value
s。更可能有很多,使用upper_bound()
,更可能是唯一的,使用find_if()
。
关于元素不在那里的好处。以下也是可行的,因为两个相同的迭代器是空的范围。注意删除下一个'vec.erase(lb,std :: upper_bound(lb,vec.end(),value));' – 2014-11-03 17:25:19
@NeilKirk是的。可能实际上更好,因为少了一个分支。 – Barry 2014-11-03 17:30:36
如果你打算调用'std :: lower_bound'和'std :: upper_bound',你可能只需调用'std :: equal_range'并将返回的迭代器对传递给'erase'。你甚至不需要对'end()'测试迭代器,空的范围只会是'erase'的一个空操作。 – Blastfurnace 2014-11-03 17:33:40
一个解决方案,使得矢量在擦除后不被排序。
// get the range in 2*log2(N), N=vec.size()
auto bounds=std::equal_range (vec.begin(), vec.end(), value);
// calculate the index of the first to be deleted O(1)
auto last = vec.end()-std::distance(bounds.first, bounds.last);
// swap the 2 ranges O(equals) , equal = std::distance(bounds.first, bounds.last)
std::swap_ranges(bounds.first, bounds.last, last);
// erase the victims O(equals)
vec.erase(last, vec.end());
std::remove
是O(N),并将该溶液也做写操作最少。如果等于接近n这个可能没那么伟大的想法:)
快速注意:如果你想有一个唯一元素的排序向量,你可能想要去一个'std :: set'。 – edmz 2014-11-03 17:07:08
@jeffamaphone不重复;该问题是关于从未排序的向量中移除许多元素,而这个问题是关于从排序的向量中移除元素,可能是在操作之后应该保持排序的元素。链接问题中的解决方案不会保留元素的相对顺序。 – cdhowie 2014-11-03 17:07:10
@black他提到他想删除所有匹配'value'的元素,这意味着他会想要一个'multiset'。 – Columbo 2014-11-03 17:08:04