我有一个std::vector<int>
和第二个容器,它将迭代器或索引(没有键,我希望不断访问该元素)保存到此向量中以进行删除。
让我们假设我有一个1000个元素的向量,并想要删除其中的200个元素。在删除操作之后,未删除元素的顺序应该相同。
我在问题的第一个版本中错过了另一件事:值是唯一的。他们是身份。
你会如何在安全(关于stl规则)和有效方式中做到这一点(对于向量的决定应该是最终的)?
可能性或方法我想到了:
vector.erase(vector.begin()+index+offset)
擦除元素,同时在容器中保留索引以计算偏移量。可以使用已删除元素的容器std::lower_bound
n来确定每次删除迭代的偏移量。 问题:由于随机位置删除,很多binary_searches用于获取偏移量和大量移动操作。 vector.erase
进行最终删除。现在我没有使任何迭代器失效,除了删除本身之外没有向量重新排列操作。 问题:大量排序 那么,你会如何解决这个问题?有什么新想法吗?有什么建议吗?
感谢您的意见。
的Sascha
编辑/更新/拥有结果:我实施了擦除删除习惯用法,KennyTM也提到了谓词基于查找一个boost :: dynamic_bitset ,它的非常快。此外,我尝试了 PigBen的移动和截断方法(也由Steve Jessop提到),它也在它的while循环中访问bitset。对我的数据来说,两者似乎同样快。我试图删除100个1000个元素(无符号整数)中的100个,这100个删除了1M次并且没有显着差异。因为我认为基于stl的擦除删除成语更“自然”,我选择了这种方法(KennyTM也提到过这个论点)。
答案 0 :(得分:13)
在<algorithm>
中,有一个remove_if
function会将所有未删除的值压缩到维持订单的前端。如果这200个元素可以完全由值而不是索引确定,则可行。
这实际上是您链接到的Erase-remove习惯用法。 remove_if
保证执行O(N)比较(并且最多是O(N)复制),这比排序(O(N log N))更有效,尽管您的最后一个选项实际上并不需要如果索引是根据值确定的,则进行排序(只需在复制时按相反方向扫描)。
尽管如此,使用remove_if
(如果可以的话)优于其他2个选项,因为已经为您编写了实现,因此逻辑错误的可能性更小,并且更好地传达 what (不是如何)。
答案 1 :(得分:13)
如何循环遍历向量,并且对于需要删除的每个元素,将不需要删除的下一个元素复制到该位置。然后当你走到最后,截断它。
int last = 0;
for(int i=0; i<vec.size(); ++i, ++last)
{
while(needs_to_be_removed(i))
++i;
if(i >= vec.size()) break;
vec[last] = vec[i];
}
vec.resize(last);
答案 2 :(得分:4)
首先,不要再调用erase
次,因为对于一个向量,它会将所有后面的元素向下混洗,从而使整个操作成为Ω(n * m)最坏情况下的运行时间(n为向量的大小,m为要删除的索引列表的大小)。
我认为我尝试的第一件事与您当前的代码类似:
indexes[0]
元素,跳过元素,然后复制indexes[1] - indexes[0] - 1
元素,跳过元素等等。swap
原始载体与新载体。您可以使用remove_copy_if
执行第三步,并使用包含状态的谓词(计算已复制的项目数以及通过排序的索引列表的距离),但< / em>由于非常繁琐和模糊的原因,这不能保证工作(具有可变状态的算法谓词是有问题的,似乎是标准不能保证相同副本的共识整个算法使用谓词)。所以我真的不建议尝试它,但它可能有助于记住你所写的内容基本上是remove_copy_if
的修改版本。
你可以避免使用back_inserter
而不是预先设定矢量的第二步,尽管你可能仍然提前预留空间。
[编辑:来想一想,我为什么要复制任何东西?不是实现修改后的remove_copy_if
,而是实现修改后的remove_if
,而只是复制到向量中的早期点。最后是erase
/ resize
。在证明是一个问题之前我不会担心O(m log m)
种索引,因为读取要删除的所有值并将它们存储在Ω(m)操作中的速度要慢得多。某种容器。然后,在remove_if
的谓词中使用此容器可能是也可能不是O(1)
。对于合理的m
值,排序可能会更快。]
答案 3 :(得分:2)
您可以将矢量的所有元素复制到列表中,除非您的第二个容器中包含索引,然后返回到矢量。即使你的算法从向量的末尾走到前面,你的向量中的幕后工作也会有很多工作。
将第二个容器设为地图,以便自动为您排序。
编辑:
回复评论
维护地图的成本与维护另一个结构(列表或向量)然后对其进行排序相同。如果你已经这样做了,你可以将它保存为地图。抱怨地图的开销与排序列表的开销没有意义。
至于我建议算法的性能,如果m是要删除的元素数,n是元素总数,则得到O(n - m)。
当然,这主要只是在幽默你用矢量进行优化的尝试。
1 - 如果要进行随机访问删除,则不应使用向量。这不是他们擅长的,如果可能的话,使用一个列表。而且由于你似乎对相对顺序而不是绝对索引更感兴趣,我想知道为什么需要一个向量。如果您解决了整个问题,可能会有一个通用的解决方案让您使用最有效的数据结构来解决它。
2 - 不要维护第二个数据结构,而是在容器中标记需要直接删除的元素。一个简单的方法是使用容器&lt; T>使用容器&lt;的std ::对&LT; T,char&gt; &GT;并使用char来跟踪元素状态。
如果您执行1和2,则会完全删除所有复制并获得更高效的实现。
答案 4 :(得分:1)
什么元素?也许我正在认真对待你的帖子,但如果你有一个1000元素的向量,为什么不标记那些无效的那些并且首先取消擦除。显然我在这里假设你的元素并不需要大量的内存。
我只是提起这件事因为你似乎关心速度。如果已经给出的建议没有做到这一点,那么这个想法值得一提!实质上是通过不首先进行操作来加快速度。
答案 5 :(得分:1)
如果您要删除一组(例如无序)索引,可以使用:
template <typename Type>
void erase_indices(
const std::unordered_set<size_t>& indices_to_erase,
std::vector<Type>& vec) {
std::vector<bool> erase_index(vec.size(), false);
for (const size_t i: indices_to_erase) {
erase_index[i] = true;
}
std::vector<bool>::const_iterator it_to_erase = erase_index.cbegin();
typename std::vector<Type>::iterator it_erase_from = std::remove_if(
vec.begin(), vec.end(),
[&it_to_erase](const Type&) -> bool {
return *it_to_erase++ == true;
}
);
vec.erase(it_erase_from, vec.end());
}
这是我想到的最快的解决方案。但是,您需要 C ++ 11 。擦除索引2和5处元素的用法示例:
constexpr size_t num = 10u;
std::vector<int> vec(num);
std::iota(vec.begin(), vec.end(), 0);
std::unordered_set<size_t> indices_to_erase;
indices_to_erase.insert(2u);
indices_to_erase.insert(5u);
erase_indices(indices_to_erase, vec);
在:
0 1 2 3 4 5 6 7 8 9
后:
0 1 3 4 6 7 8 9
修改强> 如果想要更容易保持索引要删除的容器类型:
template <typename Type, typename Container>
void erase_indices(
const Container& indices_to_erase,
std::vector<Type>& vec) {
typedef typename Container::value_type IndexType;
static_assert(std::is_same<IndexType, std::size_t>::value,
"Indices to be erased have to be of type std::size_t");
std::vector<bool> erase_index(vec.size(), false);
for (const IndexType idx_erase: indices_to_erase) {
erase_index[idx_erase] = true;
}
std::vector<bool>::const_iterator it_to_erase = erase_index.cbegin();
typename std::vector<Type>::iterator it_erase_from = std::remove_if(
vec.begin(), vec.end(),
[&it_to_erase](const Type&) -> bool {
return *it_to_erase++ == true;
}
);
vec.erase(it_erase_from, vec.end());
}
现在,只要该容器的value_type
为std::size_t
,您就可以使用Containers Library中的任何类型的容器来提供要删除的索引。用法保持不变。
答案 6 :(得分:-1)
我写了一个函数,基于Benjamin Lindley回答https://stackoverflow.com/a/4115582/2835054。
#include <iostream>
#include <algorithm>
#include <vector>
template <typename elementType, typename indexType>
void remove_multiple_elements_from_vector(std::vector<elementType> &vector,
std::vector<indexType> &indexes)
{
// 1. indexType is any integer.
// 2. elementType is any type.
// 3. Indexes should be unique.
// 4. The largest index inside indexes shouldn't be larger than
// the largetst index in the vector.
// 5. Indexes should be sorted in ascending order
// (it is done inside function).
std::sort(indexes.begin(), indexes.end());
indexType currentIndexInIndexesVector = 0;
indexType last = 0;
for(indexType i=0; i<vector.size(); ++i, ++last)
{
while(indexes[currentIndexInIndexesVector] == i)
{
++i;
++currentIndexInIndexesVector;
}
if(i >= vector.size()) break;
vector[last] = vector[i];
}
vector.resize(last);
}
int main()
{
std::vector<int> vector = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<int> indexes = {0, 10, 5};
for (auto &vectorElement : vector)
{
std::cout << vectorElement << " ";
}
std::cout << "\n";
remove_multiple_elements_from_vector<int, int>(vector, indexes);
for (auto &vectorElement : vector)
{
std::cout << vectorElement << " ";
}
}