如何从排序的矢量中有效地清除值?

问题描述:

假设vec是可移动和可复制对象的排序向量。删除所有匹配value的元素的最有效方法是什么?如何从排序的矢量中有效地清除值?

这是正确和最有效的方法吗?

auto lb = std::lower_bound(vec.begin(), vec.end(), value); 
vec.erase(lb, std::upper_bound(std::next(lb), vec.end(), value)); 

什么是复杂性? (考虑到擦除后需要的任何移动)。

+0

快速注意:如果你想有一个唯一元素的排序向量,你可能想要去一个'std :: set'。 – edmz 2014-11-03 17:07:08

+3

@jeffamaphone不重复;该问题是关于从未排序的向量中移除许多元素,而这个问题是关于从排序的向量中移除元素,可能是在操作之后应该保持排序的元素。链接问题中的解决方案不会保留元素的相对顺序。 – cdhowie 2014-11-03 17:07:10

+0

@black他提到他想删除所有匹配'value'的元素,这意味着他会想要一个'multiset'。 – Columbo 2014-11-03 17:08:04

我已经做了从排序容器擦除四种不同的方法一些简要的测试。

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_boundequal_range方法是在多次运行几乎相同。我更喜欢最新的版本,因为它是正确的,更简单的代码,并减少打字。

编辑:定时Surt's code的要求。它始终是以不保存排序顺序为代价的快速。

+0

出色的工作! – 2014-11-03 18:16:30

+0

@Blastfurnace,我是否也可以让你解决我的问题,我想知道答案以备将来参考。 – Surt 2014-11-03 19:23:13

+0

@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()

+0

关于元素不在那里的好处。以下也是可行的,因为两个相同的迭代器是空的范围。注意删除下一个'vec.erase(lb,std :: upper_bound(lb,vec.end(),value));' – 2014-11-03 17:25:19

+0

@NeilKirk是的。可能实际上更好,因为少了一个分支。 – Barry 2014-11-03 17:30:36

+3

如果你打算调用'std :: lower_bound'和'std :: upper_bound',你可能只需调用'std :: equal_range'并将返回的迭代器对传递给'erase'。你甚至不需要对'end()'测试迭代器,空的范围只会是'era​​se'的一个空操作。 – 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这个可能没那么伟大的想法:)

+0

应该是'vec.erase(last,vec.end())'? – Barry 2014-11-03 19:15:52

+0

@巴里,你说得对。 – Surt 2014-11-03 19:18:09

+0

+1 - 我认为从评论维护排序不是必需的,这是最好的答案。 – Barry 2014-11-03 19:20:14