如何有效地从排序的向量中擦除值

How to erase a value efficiently from a sorted vector?

本文关键字:向量 擦除 排序 有效地      更新时间:2023-10-16

假设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());
}

使用10,000,000个元素的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

擦除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的代码。它始终如一地快速,但代价是不保留排序顺序。

擦除后使矢量保持未排序的解决方案。

// 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,这可能不是一个好主意:)

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的可能性。更有可能拥有很多,使用upper_bound(),更有可能是唯一的,使用find_if()