列表 vs 向量,循环遍历时删除一个元素

3
我需要一个容器,可以在循环遍历时快速删除一个元素。我不需要直接访问它,因为我总是在循环内部访问它。
对于这种情况,ListVector更快吗?
伪代码:
vector<Item*> myContainer;

for(..loop over it...) {

  if (someCondition)
    myContainer.erase(currentElement)
}

删除一个元素后,我需要继续循环其余的元素。
3个回答

8

理论上,列表提供比向量更快的删除,但在实践中,没有人使用列表。为什么呢?因为向量产生的堆活动要少得多,并且它们提供更好的缓存局部性。

你确定需要循环遍历向量并逐个删除元素吗?这将导致向量具有二次复杂度,但erase-remove-idiom可以在线性时间内完成:

#include <algorithm>

myContainer.erase(
    std::remove_if(
        myContainer.begin(),
        myContainer.end(),
        [](Item* p) {
            return p->someCondition;
        }
    ),
    myContainer.end()
);

(如果向量拥有Items,您应该考虑将其替换为vector<unique_ptr<Item>>。)

作为替代方案,如果您真的必须逐个删除元素,并且不关心Items的相对顺序,则有一个小技巧可以在常数时间内删除向量中的一个元素。


@yes123 当然可以,只需将您想要对每个项目执行的操作放在 lambda 内部,在 return p->someCondition; 行之前。具有可变谓词有点不寻常,但我不认为在这种特定情况下它不能工作。或者,首先使用 for_each 更新项目,然后应用擦除/删除习语以删除不需要的项目。这样会更加清洁,但也可能会稍微影响缓存友好性。 - fredoverflow

5

使用列表在任意位置删除元素更快:

std::vector::erase的复杂度:与被删除的元素数量(析构函数)和最后一个被删除元素之后的元素数量(移动)成线性关系。

std::list::erase的复杂度:与被删除的元素数量(析构函数)成线性关系。

此外,在删除后,迭代器会变得无效,因此需要更新迭代器:

it = list.erase(it);

这也取决于向量的长度。移动已经在CPU缓存中的一堆指针(Item*)可能与释放列表节点(这也需要一些重新链接)一样快。O(1)并不总是比O(n)更快。 - Bo Persson
@yes123 - 是的,我说的是“实际应用”。使用向量通常非常快,因为它的元素在内存中相邻并最终出现在同一缓存行中。指针的链表使用的内存也会比指针的向量多3倍。我只是说移动一个大盒子(O(1))并不总是比移动n个小盒子(O(n))更好。 - Bo Persson

1
一个C++列表是一个链表。因此,元素将会像以下这样排列,
 head <--> 1 <--> 2 <--> 3 <--> 4 <--> 5

所以,当我想要删除3时,只需要移动指针。但是如果我想要删除3,我需要将2的指针指向4。在这种情况下,我需要遍历到3(找到该元素)。由于它是双向链表,并且您已经定位到要删除的元素,因此最坏情况下的时间复杂度为O(1)。

然而,另一方面,C++向量只是一个数组。因此,向量元素的删除遵循从数组中删除元素的方式。在这种情况下,您可以在O(1)的复杂度内找到要删除的元素。但是,要删除它,您需要左移所有元素以删除创建的空白。因此,最坏情况下的复杂度为O(n)。口头上的复杂度可能是“n-要删除的元素的索引”。

列表产生O(1)的删除复杂度,而向量产生O(n)的渐近复杂度。但是,列表将具有维护每个元素指针的额外开销(总体上额外的O(2n)空间)。


哦,既然你已经在迭代了,使用列表会更快。抱歉兄弟,我漏掉了那一部分。 - Mohan
继续强调我的观点(抱歉 :-)。在这个例子中,对象本身也是指针。因此,在链表中,您必须更改两个指针(在24中),然后处理包含3的节点。对于向量,您只需复制两个指针(45),然后完成操作。哪种操作最快? - Bo Persson
@BoPersson 假设列表最多有20个元素。哪个更快?列表,对吧? - Mohan
@MohanKumar - 我仍然会押注于向量。我们仍在谈论复制10或20个指针的纳秒级时间。如果是一百万条目,情况可能会有所不同。 - Bo Persson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接