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

Posted

技术标签:

【中文标题】从 std::vector 中删除多个对象?【英文标题】:Erasing multiple objects from a std::vector? 【发布时间】:2010-08-15 14:13:01 【问题描述】:

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

假设它有 50,90,40,90,80,60,80。

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

谢谢

【问题讨论】:

“排序然后用偏移量线性擦除不是一种选择”:为什么? 【参考方案1】:

我提供了几种方法:

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.另一种保留元素原始顺序的较慢方法:

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

【讨论】:

那么与自身交换是无操作的,pop_back 仍然做正确的事情。 根据 OP 不知道索引的顺序 这将重新排序矢量,这可能不是您想要的。 嗯?假设给定一个包含 10 个项目的向量,并且我想删除插槽 5:我现在将插槽 5 分配给插槽 10 中的值,然后删除插槽 10?这怎么应该是正确的答案?不是。 @C Johnson:如果其余元素的顺序无关紧要,这是正确的答案。如果它确实重要,那么您需要做更多的工作来保留订单。【参考方案2】:

使用谓词和算法 remove_if 你可以实现你想要的:见http://www.cplusplus.com/reference/algorithm/remove_if/

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

您的谓词将简单地保存每个值的 idx,以在每次返回 true 时删除和减少它保留的所有索引。

也就是说,如果您负担得起使用 remove-erase 惯用语删除每个对象的费用,那么只需这样做即可让您的生活变得简单。

【讨论】:

【参考方案3】:

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

【讨论】:

我写道:(排序然后用偏移量线性擦除不是一种选择) @Milo:除非有充分的理由任意拒绝更好的解决方案之一,否则它肯定是一种选择。为什么不能对索引进行排序?【参考方案4】:

我会将您不想想要擦除的元素移动到一个临时向量,然后用这个替换原始向量。

【讨论】:

【参考方案5】:

虽然 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

多年来(在此站点和其他类似站点上)已经提出了多种解决方案,但通常它们使用多个带有条件的“原始循环”和一些擦除/插入/push_back 调用。 stable_partition 背后的想法在 Sean Parent 的 talk 中得到了很好的解释。

这个link 提供了一个类似的解决方案(并且它不假设selection 已排序 - 使用std::find_if 而不是std::binary_search),但它也使用了一个辅助(递增)变量来禁用这种可能性在更大的数组上并行处理。

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

【讨论】:

【参考方案6】:

这行得通吗:

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) 操作,无论您删除多少个元素。您可以通过重新排列内联向量来获得一些效率(但我认为这种方式更具可读性)。

【讨论】:

【参考方案7】:

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

#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;

【讨论】:

【参考方案8】:

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

[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;

【讨论】:

以上是关于从 std::vector 中删除多个对象?的主要内容,如果未能解决你的问题,请参考以下文章

从 C++ std::vector 中删除元素

从 std::vector 中删除前 N 个元素

使用模板时如何从 std::vector 中删除元素?

C ++在向量迭代中删除并返回指向对象的指针

为啥 vector::clear 不从向量中删除元素?

如何从 std::string 中删除 +