从 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_partition
(ExecutionPolicy
)有一个新的第一个参数,它允许算法自动并行化,进一步减少大数组的运行时间。为了让自己相信这种并行化确实有效,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 中删除多个对象?的主要内容,如果未能解决你的问题,请参考以下文章