这是我的问题,假设我有一个带有整数的std :: vector。
让我们说它有50,90,40,90,80,60,80。
我知道我需要删除第二,第五和第三个元素。我不一定总是知道要删除的元素的顺序,也不知道有多少。问题是通过擦除元素,这会更改其他元素的索引。因此,我怎么能擦除这些并补偿索引变化。 (排序然后用偏移线性擦除不是一种选择)
由于
答案 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_partition
(ExecutionPolicy
)有了一个新的第一个参数,该参数允许算法自动并行化,从而进一步减少了大数组的运行时间。为了使自己相信这种并行化确实有效,还有另一种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;
}