繁体   English   中英

STL 中的向量与列表

[英]vector vs. list in STL

我在 Effective STL 中注意到

vector 是默认情况下应该使用的序列类型。

这是什么意思? 似乎忽略效率vector可以做任何事情。

任何人都可以向我提供一个场景,其中vector不是一个可行的选择,但必须使用list吗?

向量:

  • 连续内存。
  • 为未来的元素预先分配空间,因此需要超出元素本身所需的额外空间。
  • 每个元素只需要元素类型本身的空间(没有额外的指针)。
  • 可以在添加元素时为整个向量重新分配内存。
  • 最后的插入是固定的,摊销时间,但其他地方的插入是一个代价高昂的 O(n)。
  • 向量末尾的擦除是常数时间,但其余时间为 O(n)。
  • 您可以随机访问其元素。
  • 如果向向量添加元素或从向量中删除元素,则迭代器将失效。
  • 如果需要元素数组,则可以轻松获取底层数组。

列表:

  • 非连续内存。
  • 没有预先分配的内存。 列表本身的内存开销是恒定的。
  • 每个元素都需要为保存该元素的节点提供额外的空间,包括指向列表中下一个和上一个元素的指针。
  • 永远不必因为添加元素而为整个列表重新分配内存。
  • 无论插入和删除出现在列表中的哪个位置,插入和删除都很便宜。
  • 将列表与拼接结合起来很便宜。
  • 您不能随机访问元素,因此获取列表中的特定元素可能会很昂贵。
  • 即使在列表中添加或删除元素,迭代器仍然有效。
  • 如果您需要一个元素数组,则必须创建一个新元素并将它们全部添加到其中,因为没有底层数组。

通常,当您不关心使用的是哪种类型的顺序容器时,请使用 vector,但如果您在容器中的任何位置(而不是末尾)进行多次插入或擦除操作,则您会想要使用列表。 或者,如果您需要随机访问,那么您将需要向量,而不是列表。 除此之外,在某些情况下,您自然会根据您的应用程序需要一个或另一个,但总的来说,这些都是很好的指导方针。

您想要将大量项目重复插入到序列末尾以外的任何位置的情况。

查看每种不同类型容器的复杂性保证:

标准容器的复杂性保证是什么?

如果您不需要经常插入元素,那么向量会更有效。 它比列表具有更好的 CPU 缓存局部性。 换句话说,访问一个元素使得下一个元素可能出现在缓存中,并且可以在无需读取慢速 RAM 的情况下检索。

这里的大多数答案都忽略了一个重要细节:为什么?

您想在容器中保留什么?

如果它是int的集合,那么std::list将在每个场景中丢失,无论您是否可以重新分配,您只能从前面删除等等。列表的遍历速度较慢,每次插入都会花费您与分配器的交互. 准备一个例子是非常困难的,其中list<int>击败了vector<int> 即便如此, deque<int>可能更好或更接近,而不仅仅是使用列表,这将具有更大的内存开销。

但是,如果您正在处理大而难看的数据块 - 而且很少 - 您不想在插入时过度分配,并且由于重新分配而复制将是一场灾难 - 那么您可能会更好list<UglyBlob>vector<UglyBlob>

尽管如此,如果您切换到vector<UglyBlob*>甚至vector<shared_ptr<UglyBlob> > ,再次 - 列表将落后。

因此,访问模式、目标元素计数等仍然会影响比较,但在我看来 - 元素大小 - 复制成本等。

std::list 的一项特殊功能是拼接(将部分或整个列表链接或移动到不同的列表中)。

或者,如果您的内容复制起来非常昂贵。 在这种情况下,例如,使用列表对集合进行排序可能会更便宜。

另请注意,如果集合很小(并且复制内容不是特别昂贵),即使您在任何地方插入和擦除,向量仍可能优于列表。 一个列表单独分配每个节点,这可能比移动几个简单的对象要昂贵得多。

我不认为有很硬的规则。 这取决于您最想对容器做什么,以及您期望容器的大小和包含的类型。 向量通常胜过列表,因为它将其内容分配为单个连续块(它基本上是一个动态分配的数组,在大多数情况下,数组是保存一堆东西的最有效方式)。

好吧,我班的学生似乎无法向我解释什么时候使用向量更有效,但是当他们建议我使用列表时,他们看起来很高兴。

我是这样理解的

列表:每个项目都包含一个指向下一个或上一个元素的地址,因此使用此功能,您可以将项目随机化,即使它们没有排序,顺序也不会改变:如果您的内存碎片化,这很有效。 但它还有另一个非常大的优势:您可以轻松插入/删除项目,因为您唯一需要做的就是更改一些指针。 缺点:要读取随机单个项目,您必须从一个项目跳到另一个项目,直到找到正确的地址。

向量:当使用向量时,内存组织得更像常规数组:每个第 n 项都存储在第 (n-1) 项之后和第 (n+1) 项之前。 为什么它比 list 更好? 因为它允许快速随机访问。 方法如下:如果您知道向量中某项的大小,并且它们在内存中是连续的,则可以轻松预测第 n 项的位置; 您不必浏览列表的所有项目来阅读您想要的内容,使用矢量,您可以直接阅读它,而使用列表则不能。 另一方面,修改向量数组或更改值要慢得多。

列表更适合跟踪可以在内存中添加/删除的对象。 当您想从大量单个项目中访问一个元素时,向量更合适。

我不知道列表是如何优化的,但你必须知道,如果你想要快速读取访问,你应该使用向量,因为 STL 固定列表有多好,它的读取访问速度不会比向量快。

简单点——
在一天结束时,当您在 C++ 中选择容器感到困惑时,请使用此流程图图像(感谢我):-

在此处输入图片说明

向量-

  1. 载体基于传染性记忆
  2. 向量是小数据集的方法
  3. 向量在遍历数据集时执行速度最快
  4. 向量插入删除在庞大的数据集上很慢,但对于很小的数据集很快

列表-

  1. 列表基于堆内存
  2. list 是获取非常大的数据集的方法
  3. list 在遍历小数据集时相对较慢,但在遍历大数据集时速度较快
  4. 列表插入删除在庞大的数据集上很快,但在较小的数据集上很慢

任何时候都不能让迭代器失效。

基本上,向量是一个具有自动内存管理功能的数组。 数据在内存中是连续的。 尝试在中间插入数据是一项代价高昂的操作。

在列表中,数据存储在不相关的内存位置。 在中间插入并不涉及复制一些数据来为新数据腾出空间。

为了更具体地回答您的问题,我将引用此页面

向量通常是访问元素以及从序列末尾添加或删除元素的最有效时间。 对于涉及在末尾以外的位置插入或删除元素的操作,它们的性能比双端队列和列表差,并且迭代器和引用的一致性不如列表。

当序列中间有很多插入或删除时。 例如内存管理器。

保持迭代器的有效性是使用列表的原因之一。 另一种情况是当您不希望在推送项目时重新分配向量。 这可以通过智能使用 Reserve() 来管理,但在某些情况下,仅使用列表可能更容易或更可行。

当你想在容器之间移动对象时,你可以使用list::splice

例如,图分区算法可能具有在数量不断增加的容器中递归划分的恒定数量的对象。 对象应该被初始化一次并且始终保持在内存中的相同位置。 通过重新链接重新排列它们比重新分配要快得多。

编辑:当库准备实现 C++0x 时,将子序列拼接成列表的一般情况随着序列的长度变得线性复杂。 这是因为splice (现在)需要遍历序列以计算其中的元素数量。 (因为列表需要记录它的大小。)简单地计数和重新链接列表仍然比任何替代方法都快,并且拼接整个列表或单个元素是具有恒定复杂性的特殊情况。 但是,如果您有很长的序列要拼接,您可能不得不四处寻找更好的、老式的、不合规的容器。

vectorlist的情况下,我觉得主要区别如下:

向量

  • 向量将其元素存储在连续的内存中。 因此,在向量内部可以进行随机访问,这意味着访问向量的元素非常快,因为我们可以简单地将基地址与项目索引相乘以访问该元素。 事实上,为此只需要 O(1) 或常数时间。

  • 由于向量基本上包装了一个数组,因此每次将元素插入向量(动态数组)时,它都必须通过寻找新的连续内存块来调整自身大小以容纳新元素,这很费时间。

  • 它不会消耗额外的内存来存储指向其中其他元素的任何指针。

列表

  • 列表将其元素存储在非连续内存中。 因此,在列表内部不可能进行随机访问,这意味着要访问其元素,我们必须使用指针并遍历相对于向量较慢的列表。 这需要 O(n) 或比 O(1) 慢的线性时间。

  • 由于列表使用非连续内存,因此在列表中插入元素所花费的时间比在其向量对应的情况下要高效得多,因为避免了内存的重新分配。

  • 它消耗额外的内存来存储指向特定元素前后元素的指针。

因此,记住这些差异,我们通常会考虑内存、频繁的随机访问插入来决定给定场景中向量与列表的胜者。

必须使用list的唯一硬性规则是您需要分发指向容器元素的指针。

vector不同,您知道元素的内存不会被重新分配。 如果可以,那么您可能有指向未使用内存的指针,这充其量是一个很大的SEGFAULT ,最坏的是SEGFAULT

(从技术上讲, *_ptrvector也可以使用,但在这种情况下,您正在模拟list因此这只是语义。)

其他软规则与将元素插入容器中间可能出现的性能问题有关,因此list会更可取。

列表只是 stl 中双重链接列表的包装器,因此提供了您可能期望从 d-linklist 中获得的功能,即 O(1) 插入和删除。 向量是具有传染性的数据序列,其工作方式类似于动态数组。PS-更易于遍历。

总结表格中的答案以供快速参考:

向量 列表
使用权 快点 慢点
插入/删除操作 慢点 快点
内存分配 连续的 不连续
大小预分配 需要预约 不需要预约
每个元素所需的空间 仅针对元素本身 对于元素和指向下一个的指针
(以及可选的先前元素)

List 是双向链表,因此很容易插入和删除元素。 我们只需要改变几个指针,而在 vector 中,如果我们想在中间插入一个元素,那么它后面的每个元素都必须移动一个索引。 此外,如果向量的大小已满,则必须首先增加其大小。 所以这是一个昂贵的操作。 因此,在这种情况下,无论何时需要更频繁地执行插入和删除操作,都应该使用列表。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM