remove_if and then erase 在向量上是否有效?

Is remove_if and then erase efficient on vector?

虽然有几十个关于remove_if + erase for vector的问题。 我找不到这种动作的表现。 当我写:

    myVector.erase(remove_if(myVector.begin(),
                         myVector.end(),
                         some_predicate), myVector.end());

remove if 将 return 迭代器指向最后一个相关项 + 1(我们称之为 X)。 我相信这会在 O(n) 中发生。

但是擦除将如何工作?

谢谢。

vector::erase的复杂度是well-defined:

Linear: the number of calls to the destructor of T is the same as the number of elements erased, the assignment operator of T is called the number of times equal to the number of elements in the vector after the erased elements

它在内部的工作方式(即它究竟如何删除你的元素)有点无关紧要。

remove_if 的复杂度也是 defined 并且是

Exactly std::distance(first, last) applications of the predicate.

所以你的代码具有线性复杂度。

为什么不使用 swap 'n pop 方法?我们通过优化向量中的擦除进行了很多愚弄,发现这是最快的,因为它具有 O(1) 复杂性。唯一的缺点是它不保持秩序。很多情况下都很好。这是此类操作的模板方法:

template<typename T>
inline typename std::vector<T>::iterator unorderedErase(std::vector<T>& p_container,
                                                        typename std::vector<T>::iterator p_it)
{
    if (p_it != p_container.end() - 1)
    {
        std::swap(*p_it, p_container.back());
        p_container.pop_back();
        return p_it;
    }
    // else
    p_container.pop_back();
    return p_container.end();
}

考虑这个向量:

|0|1|2|3|4|5|6|7|8|9|

我们使用remove_if删除所有4的倍数的元素:

std::remove_if(v.begin(), v.end(), [](auto i){ return i != 0 && !(i%4); });

这开始使用迭代器 X 遍历向量,直到找到谓词 return 为真的元素:

|0|1|2|3|4|5|6|7|8|9|
         X

这是我们要删除的第一个元素。

接下来它创建另一个指向下一个元素的迭代器 Y = X+1,并检查 *Y:

的谓词
|0|1|2|3|4|5|6|7|8|9|
         X Y

谓词为假,所以我们想保留那个元素,所以它将下一个元素分配给我们要删除的元素,方法是 *X = std::move(*Y):

|0|1|2|3|5|5|6|7|8|9|
         X Y            *X = std::move(*Y)

所以我们有两个迭代器,X 和 Y,其中 X 指向 "output" 中的下一个元素(即我们不删除的元素),Y 是下一个要考虑删除的元素。

我们将两个迭代器移动到下一个位置,检查 Y 的谓词(再次为假),然后进行另一个赋值:

|0|1|2|3|5|6|6|7|8|9|
           X Y          *X = std::move(*Y)

然后在下一个位置再次做同样的事情:

|0|1|2|3|5|6|7|7|8|9|
             X Y       *X = std::move(*Y)

然后它继续前进,但发现谓词对 Y

成立
|0|1|2|3|5|6|7|7|8|9|
               X Y

所以它只是递增 Y,它会跳过该元素,因此不会将其复制到 X:

的 "output" 位置
|0|1|2|3|5|6|7|7|8|9|
               X   Y 

谓词对 Y 不成立,因此将其分配给 X:

|0|1|2|3|5|6|7|9|8|9|
               X   Y     *X = std::move(*Y)

然后它再次递增 X 和 Y

|0|1|2|3|5|6|7|9|8|9|
                 X   Y

现在 Y 是 past-the-end 所以我们 return X(指向输出序列的 past-the-end,即我们要保留的元素)。

remove_if returns X 之后我们调用 v.erase(X, v.end()),因此向量为从 X 到末尾的每个元素调用析构函数:

|0|1|2|3|5|6|7|9|~|~|
                 X   end

然后设置大小,使向量在 X 处结束:

|0|1|2|3|5|6|7|9|
                 end

在此之后 v.capacity() >= v.size()+2 因为最后两个元素使用的内存仍然存在,但未被使用。