从std :: vector中删除多个对象?

时间:2010-08-15 14:13:02

标签: c++ vector

这是我的问题,假设我有一个带有整数的std :: vector。

让我们说它有50,90,40,90,80,60,80。

我知道我需要删除第二,第五和第三个元素。我不一定总是知道要删除的元素的顺序,也不知道有多少。问题是通过擦除元素,这会更改其他元素的索引。因此,我怎么能擦除这些并补偿索引变化。 (排序然后用偏移线性擦除不是一种选择)

由于

8 个答案:

答案 0 :(得分:27)

我提供了几种方法:

<强> 1。一种不保留元素原始顺序的快速方法:

将向量的当前最后一个元素分配给要擦除的元素,然后擦除最后一个元素。这将避免大的移动,除了最后一个之外的所有索引将保持不变。如果从后面开始擦除,则所有预先计算的索引都是正确的。

void quickDelete( int idx )
{
  vec[idx] = vec.back();
  vec.pop_back();
}

我看到这基本上是Klaim指出的擦除删除习语的手工编码版本......

<强> 2。保留元素原始顺序的较慢方法:

步骤1:标记要删除的所有向量元素,即使用特殊值。这有O(|索引删除|)。

第2步:使用v.erase( remove (v.begin(), v.end(), special_value), v.end() );删除所有标记的元素。这有O(| vector v |)。

假设索引列表比矢量短,则总运行时间为O(| vector v |)。

第3。另一种保留元素原始顺序的较慢方法:

https://stackoverflow.com/a/3487742/280314中所述,使用谓词并删除。为了使这个有效和尊重的要求 不是“排序然后用偏移量线性擦除”,我的想法是使用哈希表实现谓词并调整存储在哈希表中的索引,因为删除继续返回true,如Klaim建议的那样。

答案 1 :(得分:13)

使用谓词和算法remove_if,您可以实现您想要的效果:请参阅http://www.cplusplus.com/reference/algorithm/remove_if/

不要忘记删除该项目(请参阅remove-erase idiom)。

您的谓词将只保留每个值的idx以删除并减少每次返回true时保留的所有索引。

如果你能负担得起只使用移除擦除习语来删除每个对象,那么只需通过这样做就可以简化你的生活。

答案 2 :(得分:7)

向后删除项目。换句话说,首先擦除最高索引,然后擦除下一个最高等等。您不会使任何先前的迭代器或索引无效,因此您可以使用多个擦除调用的明显方法。

答案 3 :(得分:5)

我会将您想要删除的元素移动到临时矢量,然后用此替换原始矢量。

答案 4 :(得分:1)

这会有效吗?

void DeleteAll(vector<int>& data, const vector<int>& deleteIndices)
{
    vector<bool> markedElements(data.size(), false);
    vector<int> tempBuffer;
    tempBuffer.reserve(data.size()-deleteIndices.size());

    for (vector<int>::const_iterator itDel = deleteIndices.begin(); itDel != deleteIndices.end(); itDel++)
        markedElements[*itDel] = true;

    for (size_t i=0; i<data.size(); i++)
    {
        if (!markedElements[i])
            tempBuffer.push_back(data[i]);
    }
    data = tempBuffer;
}

这是一个O(n)操作,无论你删除多少元素。您可以通过重新排序内联向量来获得一些效率(但我认为这样更可读)。

答案 5 :(得分:0)

如果其余元素的顺序无关紧要,则可以使用此方法

#include <iostream> 
#include <vector>

using namespace std;
int main()
{
    vector< int> vec;
    vec.push_back(1);
    vec.push_back(-6);
    vec.push_back(3);
    vec.push_back(4);
    vec.push_back(7);
    vec.push_back(9);
    vec.push_back(14);
    vec.push_back(25);
    cout << "The elements befor " << endl;
    for(int i = 0; i < vec.size(); i++) cout << vec[i] <<endl;
    vector< bool> toDeleted;
    int YesOrNo = 0;
    for(int i = 0; i<vec.size(); i++)
    {
        
        cout<<"You need to delete this element? "<<vec[i]<<", if yes enter 1 else enter 0"<<endl;
        cin>>YesOrNo;
        if(YesOrNo)
            toDeleted.push_back(true);
        else
            toDeleted.push_back(false);
    }
    //Deleting, beginning from the last element to the first one
    for(int i = toDeleted.size()-1; i>=0; i--)
    {
        if(toDeleted[i])
        {
            vec[i] = vec.back();
            vec.pop_back();
        }
    }
    cout << "The elements after" << endl;
    for(int i = 0; i < vec.size(); i++) cout << vec[i] <<endl;
    return 0;
}

答案 6 :(得分:0)

当您不需要保留订单时,Peter G.的this answer在变体一中(交换和弹出技术)是最快的,这是保持订单的未提及的替代方法。

>

使用C ++ 17和C ++ 20,可以使用标准算法从向量中删除多个元素。由于std::stable_partition,运行时间为O(N * Log(N))。没有外部帮助程序数组,没有过多的复制,一切都就地完成。代码是“单线”:

template <class T>
inline void erase_selected(std::vector<T>& v, const std::vector<int>& selection)
{
    v.resize(std::distance(
        v.begin(),
        std::stable_partition(v.begin(), v.end(),
             [&selection, &v](const T& item) {
                  return !std::binary_search(
                      selection.begin(),
                      selection.end(),
                      static_cast<int>(static_cast<const T*>(&item) - &v[0]));
        })));
}

上面的代码假定selection向量是经过排序的(如果不是这种情况,显然std::sort可以完成工作)。

要对此进行分解,让我们声明一些临时对象:

// We need an explicit item index of an element
// to see if it should be in the output or not
int itemIndex = 0;
// The checker lambda returns `true` if the element is in `selection`
auto filter = [&itemIndex, &sorted_sel](const T& item) {
    return !std::binary_search(
                      selection.begin(),
                      selection.end(),
                      itemIndex++);
};

然后将此检查器lambda馈送到std::stable_partition算法,该算法保证对原始(未置换!)数组v中的每个元素仅调用一次此lambda。

auto end_of_selected = std::stable_partition(
                           v.begin(),
                           v.end(),
                           filter);

end_of_selected迭代器指向应该保留在输出数组中的最后一个元素之后,因此我们现在可以缩小v的大小。要计算元素数量,我们使用std::distance从两个迭代器中获取size_t

v.resize(std::distance(v.begin(), end_of_selected));

这与顶部的代码不同(它使用itemIndex来跟踪数组元素)。为了摆脱itemIndex,我们捕获了对源数组v的引用,并使用指针算法在内部计算itemIndex

多年来(在这个站点和其他类似站点上)已经提出了多种解决方案,但是通常它们会使用多个“原始循环”,并附带条件和一些“擦除/插入/推回”调用。 talk的肖恩·帕恩特(Sean Parent)很好地解释了stable_partition背后的想法。

link提供了类似的解决方案(并且不假定selection已排序-使用std::find_if而不是std::binary_search),但它也使用了一个辅助方法(递增)变量,从而无法并行处理较大数组上的处理。

从C ++ 17开始,std::stable_partitionExecutionPolicy)有了一个新的第一个参数,该参数允许算法自动并行化,从而进一步减少了大数组的运行时间。为了使自己相信这种并行化确实有效,还有另一种talk的作者Hartmut Kaiser解释了内部原理。

答案 7 :(得分:0)

这很重要,因为当您从向量中删除元素时,索引会发生变化。

[0] hi
[1] you
[2] foo

>> delete [1]
[0] hi
[1] foo

如果您保留删除元素的次数计数器,并且如果您有一个要删除的索引列表,则按排序顺序:

int counter = 0;
for (int k : IndexesToDelete) {
  events.erase(events.begin()+ k + counter);
  counter -= 1;
}