矢量与来自 STL 的列表 - 删除方法

vector vs. list from stl - remove method

本文关键字:列表 删除 方法 STL      更新时间:2023-10-16

std::list有一个remove方法,而std::vector没有。这是什么原因呢?

>std::list<>::remove是一种物理删除方法,可以通过物理销毁满足某些条件的列表元素来实现(物理销毁是指元素存储持续时间的结束)。物理删除仅适用于列表。它不能应用于数组,如 std::vector<> .根本不可能以物理方式结束数组中单个元素的存储持续时间。数组只能作为一个整体创建和销毁。这就是为什么std::vector<>没有类似于std::list<>::remove的东西。

适用于所有可修改序列的通用删除方法是所谓的逻辑删除:通过用位于序列中更下方的元素值覆盖目标元素的值,从序列中"删除"目标元素。 即,通过向"向左"复制持久数据来移动和压缩序列。这种逻辑删除是通过独立函数实现的,例如 std::remove 。这些功能同样适用于std::vector<>std::list<>

在基于立即物理删除特定元素的方法适用的情况下,它将比我上面提到的逻辑删除的通用方法更有效。这就是为什么值得专门为std::list<>提供它的原因.

>std::list::remove删除列表中与提供的值匹配的所有项目。

std::list<int> myList;
// fill it with numbers
myList.remove(10); // physically removes all instances of 10 from the list

它有一个类似的函数, std::list::remove_if ,它允许您指定一些其他谓词。

std::list::remove(物理删除元素)需要成为成员函数,因为它需要了解内存结构(即,它必须更新需要更新的每个项目的上一个和下一个指针,并删除项目),并且整个函数是线性时间完成的(列表的单次迭代可以删除所有请求的元素,而不会使任何指向剩余项目)。

不能从 std::vector 中物理删除单个元素。 您可以重新分配整个向量,也可以将每个元素移动到已删除的项目之后并调整size成员。 这组操作的"最干净"实现是

// within some instance of vector
void vector::remove(const T& t)
{
    erase(std::remove(t), end());
}

这将要求std::vector依赖于<algorithm>(目前不需要)。

由于需要"排序"来删除项目,而无需多次分配和副本。 (您无需对列表进行排序即可物理删除元素)。

与其他人所说的相反,这与速度无关。 它与需要知道数据如何在内存中存储的算法有关。

作为旁注:这也是为什么std::remove(和朋友)实际上不会从他们操作的容器中删除物品的类似原因;他们只是将所有不会移除的物品移动到容器的"前面"。 如果不知道如何从容器中实际删除对象,通用算法实际上无法删除。

考虑这两个容器的实现细节。vector必须提供连续的内存块进行存储。为了删除索引 n != N 处的元素(N 是向量的长度),需要移动从 n+1N-1 的所有元素。<algorithm>标头中的各种函数实现该行为,如std::removestd::remove_if。这些独立函数的优点是它们可以适用于提供所需迭代器的任何类型。

另一方面,list是作为链表结构实现的,因此:

  • 从任何地方删除元素都很快
  • 使用迭代器不可能有效地做到这一点(因为必须知道和操纵内部结构)。

一般来说,在STL中,逻辑是"如果它可以有效地完成 - 那么它就是一个类成员。如果它效率低下 - 那么它是一个外部功能"

通过这种方式,他们区分了类的"正确"(即"有效")使用与"不正确"(低效)的使用。

例如,随机访问迭代器具有 += 运算符,而其他迭代器使用 std::advance 函数。

在这种情况下 - 从std::list中删除元素非常有效,因为您不需要像在std::vector中那样移动剩余的值

这一切都与效率和引用/指针/迭代器有效性有关。 可以在不干扰任何其他指针和迭代器的情况下删除list项。 除了最微不足道的情况外,对于vector和其他容器来说,情况并非如此。没有什么可以阻止使用外部策略,但您有更好的选择。也就是说,这个家伙在一个重复的问题上说得比我好

来自另一张关于重复问题的海报:

问题不在于为什么 std::vector 不提供操作,而是 而是为什么 std::list 提供它。STL的设计重点突出 关于容器的分离和算法 迭代器,以及在可以实现算法的所有情况下 在迭代器方面有效,这是选项。

但是,在某些情况下,某些特定操作可以 通过了解容器更有效地实现。 这就是从容器中删除元素的情况。成本 使用删除-擦除习惯用法在容器的大小上是线性的 (不能减少太多),但这掩盖了这样一个事实,即在 最坏的情况是,除了其中一个操作之外,所有操作都是对象的副本 (唯一匹配的元素是第一个),这些副本可以 代表相当大的隐性成本。

通过将操作实现为 std::list 中的方法,复杂性 的操作仍将是线性的,但相关成本 删除的每个元素都非常低,几个指针 复制并释放内存中的节点。与此同时, 作为列表的一部分实施可以提供更强大的保证: 指向未擦除元素的指针、引用和迭代器 不会在操作中失效。

在特定的算法中实现的另一个示例 容器是 std::list::sort,它使用较少的合并排序 比 std::sort 高效,但不需要随机访问迭代器。

所以基本上,算法是作为自由函数实现的 迭代器,除非有充分的理由提供特定的 在混凝土容器中实施。

std::list

设计为像链表一样工作。也就是说,它是为恒定时间插入和删除而设计的(您可能会说是优化的)......但访问相对较慢(因为它通常需要遍历列表)。

std::vector 专为恒定时间访问而设计,就像数组一样。 因此,它针对随机访问进行了优化...但是插入和移除实际上只应该在"尾巴"或"末端"完成,在其他地方它们通常会慢得多。

具有不同用途的不同数据结构...因此不同的操作。

要从容器中删除元素,您必须先找到它。排序向量和未排序向量之间存在很大差异,因此通常无法为向量实现有效的删除方法。