从向量中删除元素


101

我想使用擦除方法从向量中清除元素。但是这里的问题是不能保证元素在向量中仅出现一次。它可能存在多次,我需要清除所有这些。我的代码是这样的:

void erase(std::vector<int>& myNumbers_in, int number_in)
{
    std::vector<int>::iterator iter = myNumbers_in.begin();
    std::vector<int>::iterator endIter = myNumbers_in.end();
    for(; iter != endIter; ++iter)
    {
        if(*iter == number_in)
        {
            myNumbers_in.erase(iter);
        }
    }
}

int main(int argc, char* argv[])
{
    std::vector<int> myNmbers;
    for(int i = 0; i < 2; ++i)
    {
        myNmbers.push_back(i);
        myNmbers.push_back(i);
    }

    erase(myNmbers, 1);

    return 0;
}

这段代码显然崩溃了,因为我在遍历向量的同时更改了向量的结尾。实现此目标的最佳方法是什么?即有什么方法可以执行此操作,而无需多次遍历向量或创建向量的另一个副本?

Answers:


167

使用删除/擦除习惯用法

std::vector<int>& vec = myNumbers; // use shorter name
vec.erase(std::remove(vec.begin(), vec.end(), number_in), vec.end());

发生的情况是remove压缩了与要删除的值(number_in)开头不同的元素。vector并将迭代器返回到该范围之后的第一个元素。然后erase删除这些元素(其值未指定)。


2
std::remove()移位元素,以使要删除的元素被覆盖。该算法不会更改容器的大小,并且如果n删除了元素,那么最后的n元素是不确定的。
威廉希尔

20
斯科特·迈耶斯(Scott Meyers)在“有效的STL:改进标准模板库使用的50种特定方法”一书中的第32条中介绍了擦除删除习惯用法。
亚历山德罗·贾科普森

1
@Benjin不需要更新,它将调用对象的析构函数(如果存在)。
Motti

54
这样的STL“惯用语”使我将Python用于小型项目。
Johannes Overmann 2013年

1
@LouisDionne指的是一个迭代器的重载,我正在使用两个迭代器的重载
Motti

53

调用擦除将使迭代器无效,您可以使用:

void erase(std::vector<int>& myNumbers_in, int number_in)
{
    std::vector<int>::iterator iter = myNumbers_in.begin();
    while (iter != myNumbers_in.end())
    {
        if (*iter == number_in)
        {
            iter = myNumbers_in.erase(iter);
        }
        else
        {
           ++iter;
        }
    }

}

或者您可以将std :: remove_if与函子和std :: vector :: erase一起使用:

struct Eraser
{
    Eraser(int number_in) : number_in(number_in) {}
    int number_in;
    bool operator()(int i) const
    {
        return i == number_in;
    }
};

std::vector<int> myNumbers;
myNumbers.erase(std::remove_if(myNumbers.begin(), myNumbers.end(), Eraser(number_in)), myNumbers.end());

在这种情况下,您可以使用std :: remove来代替编写自己的函子:

std::vector<int> myNumbers;
myNumbers.erase(std::remove(myNumbers.begin(), myNumbers.end(), number_in), myNumbers.end());

在C ++ 11中,您可以使用lambda代替函子:

std::vector<int> myNumbers;
myNumbers.erase(std::remove_if(myNumbers.begin(), myNumbers.end(), [number_in](int number){ return number == number_in; }), myNumbers.end());

在C ++ 17 中,也可以使用std :: experimental :: erasestd :: experimental :: erase_if,在C ++ 20中,它们(最终)被重命名为std :: erasestd :: erase_if

std::vector<int> myNumbers;
std::erase_if(myNumbers, Eraser(number_in)); // or use lambda

要么:

std::vector<int> myNumbers;
std::erase(myNumbers, number_in);

2
当可以使用equal_to时,为什么还要使用自己的函子?:-P sgi.com/tech/stl/equal_to.html
克里斯·杰斯特·杨

3
顺便说一句,调用erasewith remove是完成此操作的规范方法。
康拉德·鲁道夫

1
我认为他就是这么做的。但如果使用自己的函子iirc,则应使用remove_if。或仅在没有函子的情况下使用删除
Johannes Schaub-litb

3
+1拼写清楚的代码仅在编程竞赛中对我有所帮助,而“仅使用remove-erase惯用语”则没有帮助。

13
  1. 您可以使用索引访问进行迭代,

  2. 为了避免O(n ^ 2)复杂性,可以使用两个索引:i-当前测试索引,j-索引来存储下一项,并在循环结束时使用向量的新大小。

码:

void erase(std::vector<int>& v, int num)
{
  size_t j = 0;
  for (size_t i = 0; i < v.size(); ++i) {
    if (v[i] != num) v[j++] = v[i];
  }
  // trim vector to new size
  v.resize(j);
}

在这种情况下,没有迭代器无效,复杂度为O(n),代码非常简洁,并且不需要编写一些帮助程序类,尽管在某些情况下使用帮助程序类可以使代码更灵活。

此代码不使用erase方法,但可以解决您的任务。

使用纯stl,您可以通过以下方式执行此操作(这与Motti的答案类似):

#include <algorithm>

void erase(std::vector<int>& v, int num) {
    vector<int>::iterator it = remove(v.begin(), v.end(), num);
    v.erase(it, v.end());
}

4

根据执行此操作的原因,使用std :: set可能比std :: vector更好。

它允许每个元素仅出现一次。如果您多次添加,则无论如何只有一个实例要擦除。这将使擦除操作变得微不足道。擦除操作的时间复杂度也将低于矢量,但是,在集合上添加元素的速度较慢,因此可能没有太大优势。

如果您对将元素添加到向量中的次数或元素添加的顺序感兴趣,这当然是行不通的。


-1

要删除第一个元素,您可以使用:

vector<int> mV{ 1, 2, 3, 4, 5 }; 
vector<int>::iterator it; 

it = mV.begin(); 
mV.erase(it); 

1
那不是问题。您11年以后的答案似乎无关紧要,阿米特!
带翅膀的小行星
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.