【问题标题】:array vs vector vs list数组 vs 向量 vs 列表
【发布时间】:2010-12-26 16:47:15
【问题描述】:

我正在维护一个包含 10 个条目的固定长度表。每个项目都是类似 4 个字段的结构。将有插入、更新和删除操作,由数字位置指定。我想知道哪个是用于维护此信息表的最佳数据结构:

  1. 数组 - 由于移位,插入/删除需要线性时间;更新需要恒定的时间;没有空间用于指针;使用 [] 访问项目更快。

  2. stl 向量 - 由于移位,插入/删除需要线性时间;更新需要恒定的时间;没有空间用于指针;访问项目比数组慢,因为它是对 operator[] 和链表的调用。

  3. stl 列表 - 插入和删除需要线性时间,因为您需要在应用插入/删除之前迭代到特定位置;指针需要额外的空间;访问项比数组慢,因为它是链表线性遍历。

现在,我的选择是使用数组。合理吗?还是我错过了什么?

哪个更快:遍历一个列表,然后插入一个节点或移动数组中的项目以产生一个空位置,然后将项目插入该位置?

衡量这种性能的最佳方法是什么?我可以只显示操作前后的时间戳吗?

【问题讨论】:

  • 如果数组是固定大小的,为什么还需要进行任何数组移位?
  • 你实际实现了什么?
  • @Myles:这就像一个排名信息。把它想象成一个按升序排列的 10 个价格的列表。如果一个新的价格来了,我必须把它插入中间等级,那么当前的最后一个价格就必须走。所以我需要调动一些职位,为新的排名让路。
  • “访问一个项目比一个数组慢,因为它是对 operator[] 的调用” - 好的,停在那里。
  • 另一个选项是 std::array,如果长度确实是固定的。

标签: c++ arrays list stl vector


【解决方案1】:

使用 STL vector。它提供了与list 一样丰富的接口,并消除了管理数组所需的内存的痛苦。

您将不得不非常努力地暴露operator[] 的性能成本 - 它通常会被内联。

我没有任何数字可以告诉你,但我记得读过性能分析,其中描述了 vector<int> 比 list<int> 快,即使对于插入和删除(当然在一定大小下)也是如此。事实是,我们使用的这些处理器非常快——如果你的向量适合 L2 缓存,那么它会非常非常快。另一方面,列表必须管理会杀死 L2 的堆对象。

【讨论】:

  • 我在某篇文章的某处读到它正在循环超过 1000 个比较数组和向量的项目,在向量的情况下,有人提到它需要更长的时间......答案说这是因为它是一个模板,使用 operator[] 会产生开销...?
  • @jasonline 我在为 iPhone 编写的游戏中使用了矢量。我从来没有遇到过使用 operator[] 的性能问题。这些就是我们所说的“微优化”——它们是微的,因为它们对应用程序的整体性能几乎没有影响。虽然我花了数年时间学习微优化的课程,但我强烈建议您避免使用它们并专注于创建出色的应用。
  • 实际上对于插入和删除 如果您必须搜索元素,vector 总是比 list 快,尤其是在大型数据集上。虽然向量操作总是 O(N) 并且列表操作平均为 O(N/2),但列表操作主要是无序的内存访问时间,这使得它们单独比向量版本使用的额外 N/2 操作更昂贵.
  • 好的,你提到 vector 在一定大小下更快。在什么情况下 list 会比 vector 更快?
  • Bjarne Stroustrup 对 list 持批评态度,并在此处偏爱 vector :channel9.msdn.com/Events/GoingNative/GoingNative-2012/…
【解决方案2】:

过早的优化是万恶之源。

根据您的帖子,我认为您没有理由在这里选择基于性能的数据结构。当且仅当性能测试表明这是一个问题时,选择最方便的并返回以更改它。

【讨论】:

  • 是的,我知道在这种情况下性能并不重要,因为它只是一些数据,但我只是想知道......
  • 当您编写小型应用程序时,您会遇到 Log(1) 问题。当你掌握了基础知识后,你也可以开始知道你需要扩展什么。当您也知道需要扩展什么时,而不是一些理论上的限制,例如......“我们需要在未来扩展”。然后您可以选择适合的算法和数据结构。在那之前,只需使用 std::vector。
【解决方案3】:

您做出了不应该做出的假设,例如“访问项目比数组慢,因为它是对 operator[] 的调用。”我可以理解其背后的逻辑,但在我们对其进行分析之前,您和我都无法知道。

如果这样做,您会发现在启用优化时根本没有任何开销。编译器内联函数调用。 内存性能存在差异。数组是静态分配的,而向量是动态分配的。一个列表为每个节点分配,如果你不小心,它可能会限制缓存。

一些解决方案是从堆栈中分配vector,并为list 分配池分配器,以便节点可以放入缓存中。

因此,您不必担心不受支持的声明,而应该担心使您的设计尽可能简洁。那么,哪个更有意义?数组、向量还是列表?我不知道你想做什么,所以我无法回答你。

“默认”容器往往是vector。有时数组也是完全可以接受的。

【讨论】:

  • 是的,我同意默认容器是矢量。
【解决方案4】:

更喜欢 std::vector 而不是数组。矢量的一些优点是:

  • 它们在大小增加时从可用空间中分配内存。
  • 它们不是变相的指针。
  • 它们可以增加/减少运行时间的大小。
  • 他们可以使用 at() 进行范围检查。
  • 矢量知道它的大小,因此您不必计算元素。

使用向量最令人信服的原因是它可以让您从显式内存管理中解放出来,并且不会泄漏内存。向量跟踪它用来存储其元素的内存。当向量需要更多内存来存储元素时,它会分配更多;当向量超出范围时,它会释放该内存。因此,用户无需关心向量元素的内存分配和释放。

【讨论】:

  • 关于大小,我认为在这种情况下它并不重要,因为我保持 10 的固定大小。我同意 at() 在访问元素时提供安全性。
  • @jasonline 好吧,如果您只想维护 10 个元素的列表并且不需要标准库提供的便利(如模板),只需使用静态分配的数组。无论如何,C++ 是一种灵活的语言!
  • 该问题明确表示该表是固定大小的,所以我推荐 std::array,它提供了 std::vector 的好处,而无需调整大小的开销。为什么要为你不会使用的东西付费?
【解决方案5】:

首先要注意几点:

关于选择数据结构的一个好的经验法则:通常,如果您检查了所有可能性并确定数组是您的最佳选择,请重新开始。你做错了什么。

STL 列表不支持operator[],如果他们支持它比索引数组慢的原因与函数调用的开销无关。

话虽如此,vector 显然是这里的赢家。对operator[] 的调用基本上可以忽略不计,因为向量的内容保证在内存中是连续的。它支持insert() 和erase() 操作,如果您使用数组,您基本上必须自己编写这些操作。基本上它归结为这样一个事实,向量本质上是一个升级的数组,它已经支持你需要的所有操作。

【讨论】:

  • 是的,你是对的。列表没有运算符 [],我的错误。是什么让我对数组的选择错了?由于向量运算符 [] 以及与向量相比我仍然需要在数组中实现操作的想法。对吗?
  • Vijay Mathew 很好地概述了向量相对于数组的好处,但也要记住,由于向量在内存中保持其内容连续,因此数组对向量没有任何好处。假设 operator[] 的性能比索引数组的性能更差并不是完全错误的,但在引擎盖下,编译器可能会将它们减少到完全相同的东西。
【解决方案6】:

确实值得花一些时间来了解列表和向量之间的根本区别。 两者之间最显着的区别在于它们存储元素和跟踪它们的方式。

- 列表-

List 包含的元素具有存储在其中的前一个和下一个元素的地址。这意味着无论列表大小如何,您都可以以恒定的速度 O(1) 在列表中的任何位置插入或删除元素。您还可以在任何地方以恒定速度拼接(插入另一个列表)到现有列表中。原因是列表只需要更改我们要插入列表的元素的两个指针(上一个和下一个)。

如果您需要随机访问,列表并不好。因此,如果一个人计划访问列表中的第 n 个元素 - 必须一个一个地遍历列表 - O(n) 速度

- 向量 -

Vector 按顺序包含元素,就像数组一样。这对于随机访问非常方便。访问向量中的“第 n 个”元素是一个简单的指针计算(O(1) 速度)。但是,向向量添加元素是不同的。如果想在向量中间添加一个元素 - 该元素之后的所有元素都必须重新分配,以便为新条目腾出空间。速度将取决于矢量大小和新元素的位置。最坏的情况是在向量中的位置 2 处插入一个元素,最好的情况是添加一个新元素。因此 - 插入以 O(n) 的速度工作,其中“n”是需要移动的元素的数量 - 不一定是向量的大小。

还有涉及内存要求等的其他差异,但了解列表和向量如何实际工作的这些基本原则确实值得花一些时间。

一如既往......“过早的优化是万恶之源”所以首先考虑什么更方便,让事情完全按照你想要的方式工作,然后进行优化。对于您提到的 10 个条目——不管你使用什么——无论你使用什么方法,你都永远无法看到任何类型的性能差异。

【讨论】:

    【解决方案7】:

    我正在维护一个包含 10 个条目的固定长度表。每一项都是一个 类似4个字段的结构。会有插入、更新和删除 操作,由数字位置指定。我想知道哪个是 用于维护此信息表的最佳数据结构:

    基于此描述,似乎列表可能是更好的选择,因为在数据结构中间插入和删除时它的 O(1)。不幸的是,在使用列表进行插入和删除时,您不能使用数字位置,就像对数组/向量一样。这种困境导致了一系列问题,这些问题可用于初步决定哪种结构可能最适合使用。如果测试清楚地表明它的选择错误,则以后可以更改此结构。

    您需要提出三个问题。首先是相对于随机读取,您计划多久在中间进行一次删除/插入。第二个是与迭代器相比,使用数字位置的重要性。最后,结构中的顺序很重要。

    如果第一个问题的答案是随机读取,则可能比向量/数组更普遍。请注意,即使使用 operator[] 表示法,遍历数据结构也不会被视为随机读取。对于第二个问题,如果您绝对需要数字位置,则将需要向量/数组,即使这可能会导致性能下降。稍后的测试可能表明,相对于使用数字位置更容易编码,这种性能损失可以忽略不计。最后,如果顺序不重要,您可以使用 O(1) 算法在向量/数组中插入和删除。示例算法如下所示。

    template <class T>
    void erase(vector<T> & vect, int index) //note: vector cannot be const since you are changing vector
    {
      vect[index]= vect.back();//move the item in the back to the index
      vect.pop_back(); //delete the item in the back
    }
    
    template <class T>
    void insert(vector<T> & vect, int index, T value) //note: vector cannot be const since you are changing vector
    {
      vect.push_back(vect[index]);//insert the item at index to the back of the vector
      vect[index] = value; //replace the item at index with value
    }
    

    【讨论】:

      【解决方案8】:

      如果需要随机访问数据并添加到最后一个元素使用数组(向量具有动态分配但如果您需要更多操作作为排序、调整大小等,请使用矢量)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-11-10
        • 1970-01-01
        • 2014-08-08
        • 1970-01-01
        • 2015-10-24
        • 2013-03-09
        • 1970-01-01
        相关资源
        最近更新 更多