【问题标题】:C++ STL list vs setC++ STL 列表与集合
【发布时间】:2011-01-19 03:34:41
【问题描述】:

对于随机插入和删除,这两者中哪一个更快?

我的猜测是列表。

虽然在集合的情况下将值作为键也很有吸引力。

迭代整个容器的性能是否相似?

【问题讨论】:

    标签: c++ list stl containers


    【解决方案1】:

    首先考虑语义,然后考虑性能。

    如果您有一组整数,并且您将整数 6、8、13、8、20、6 和 50 插入其中,您最终将得到一个包含以下五个元素的集合:{ 6, 8, 13, 20, 50 }.

    如果您使用列表执行此操作,您最终会得到一个包含以下七个元素的列表:{ 6, 8, 13, 8, 20, 6, 50 }

    那么,你想要什么?用如此不同的语义比较容器的速度是没有意义的。

    【讨论】:

    • 就我的目的而言,两者都可以工作。-无论如何,我认为我对列表感觉更舒服。谢谢!
    • 如果两者都有效,那么您的意思是顺序无关紧要。这实际上是一组的一个点,而不是一个列表。您不应该根据自己的喜好来选择容器:它们在界面方面都非常相似,所以如果您对其中一个感到满意,那么您几乎对所有容器都感到满意。
    【解决方案2】:

    列表

    1. 搜索(线性时间)。
    2. 插入、删除、移动(需要固定时间)。
    3. 可以订购元素。
    4. 元素可以排序。
    5. 元素可能重复。

    设置

    1. 正在搜索(大小为对数)。
    2. 插入和删除(一般为对数)。
    3. 元素是无序的。
    4. 元素总是从低到高排序。
    5. 元素是独一无二的。

    【讨论】:

    • 在集合中,元素是有序的,而不是按插入顺序。它们已排序。
    • @TokenMacGuy, @Dave17 C++ 集合确实是排序的,但是它们是not 排序的,因为您不能通过将任意元素放在任意位置。这是“有序”和“排序”之间的主要区别。从数学意义上看集合要好得多:它们根本没有顺序,句号。它们被排序的事实是一个实现细节,只是为了提高效率。底线:如果顺序无关紧要但唯一元素重要,则使用集合。
    • @wilhelmtell:集合的排序属性不是任意的实现细节,而是库的固定保证。另一个集合std::hashset 提供集合语义,但没有按顺序遍历。不同的容器适用于不同的使用模式。
    【解决方案3】:

    如果您关心速度,那么您可能应该使用std::vectorstd::list 每次插入一个元素时都会执行一次堆分配,这通常是一个瓶颈。

    一个例外是复制单个项目非常昂贵,或者当您拥有大量项目时。在这些情况下,列表可能会表现得更好,因为它在调整大小时不必移动项目。 std::deque 在这里也是一个不错的选择,但您需要分析您的应用程序才能在两者之间做出决定。

    最后,仅当您需要对项目进行排序(或者如果您不想重复项目)时才使用std::set。否则它会比列表或向量慢很多。

    【讨论】:

    • std::set 还有其他优点,比如如果你不想在集合中出现欺骗
    • 嗯,我可以使用带有预分配内存的列表。 (使用自定义分配器)。我对向量最大的担忧是在随机位置删除,因为我害怕每次这样做时它都会重新分配整个数组。这是一个问题吗?
    • @mokaschitta:据我了解,当向量增长时,当您删除元素甚至清除整个数组时,它们会保留分配的内存。我不知道这种行为是标准规定的,还是通常实施的(我相信有人会介入并澄清)。在任何情况下,您通常都需要借助这个技巧(不删除向量对象本身)从以前的大向量中回收内存:vec.swap(vector<int>());
    • @mokaschitta - 如果您担心分配问题,您应该考虑使用内存池(检查 Boost),甚至更好的侵入式容器(再次检查 Boost)
    • 在某些情况下,使用std::list::splice,您可以避免插入时分配(没有自定义分配器)。
    【解决方案4】:

    在 std::list 中,插入和删除本身需要 O(1) 的时间,这意味着 非常快,而且最重要的是速度不取决于数量列表中的元素。

    在 std::set 中,插入和删除需要花费 O(log(N)) 的时间,这意味着 如果集合中包含很多元素,则速度会稍慢。表达式 O(log(N)) 中的 N 表示元素的数量。 Grosso modo,表示运算所花费的时间与元素个数的对数(底数在这里无关,因为它相当于乘以一个常数,在理论算法分析中忽略)在集合中。

    但重要的是要考虑到找到要删除的元素所花费的时间。如果您必须在容器中搜索要删除的元素,这很可能是这种情况,那么 std::list 将花费相当长的时间进行此搜索,这将在 O(N) 中(这意味着 不是快,因为时间与元素的数量成正比,而不是它的对数),而 std::set 将花费 O(log N) 的时间进行搜索。

    还要注意,这些理论分析对于元素很少的容器绝对无效,在这种情况下,它们隐藏的乘法常数变得比它关注的时间函数族更重要。

    简而言之: std::list => 搜索要删除的元素较慢;更快地删除它。 std::set => 更快地搜索要删除的元素;删除它的速度较慢。

    但对于整个操作,以及大量元素,std::set 更好。

    您还应该考虑使用哈希表。 Boost、Qt 或 C++0x 中提供了这些的良好实现。他们及时完成所有这些操作,趋向于 O(1)(这意味着 非常非常快)。

    【讨论】:

    • 哈希永远不会是 O(1)。甚至没有好的。当然,除非您定制一个只能接受有限键集的散列函数。
    【解决方案5】:

    您应该根据实际数据的实际使用情况自行衡量性能。检查典型和最坏情况的性能。

    虽然 std::vector 随机插入的时间复杂度为 O(N),std::set O(log(N)) 和 std::list O(1),但 std::vector 在许多情况下表现最好。仅当性能不够重要而无法花时间测量时,才考虑大 O 复杂度。

    “如果你不测量你就不是工程”(Rico Mariani)

    【讨论】:

      【解决方案6】:

      std::list 对于插入和删除来说是 O(1)。但是您可能需要 O(n) 才能找到插入或删除点。 std::set 是 O(log(n)) 的插入和删除,它通常实现为红黑树。

      考虑找到插入/删除点以做出选择。

      【讨论】:

      • 谢谢!所以你能说如果我能接受集合的语义,它很可能比列表更快吗?还是我误解了 O(log(n))?
      • 我不知道,您没有指定如何访问列表以查找插入/删除点。如果你已经有一个恰好在正确位置的迭代器,那么 O(1) 是不可能被击败的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-21
      相关资源
      最近更新 更多