【问题标题】:Time complexity of removing items in vectors and deque删除向量和双端队列中项目的时间复杂度
【发布时间】:2015-03-31 17:29:16
【问题描述】:

我已经阅读到将项目添加到 std::vector 末尾的时间复杂度是摊销常数,并且在 std::deque 的顶部和底部插入项目是常数。因为这两个容器都有一个随机访问迭代器,因此访问元素在任何索引处都是恒定的。如果我有任何这些事实错误,请告诉我。我的问题是,如果访问 std::vectorstd::deque 中的元素是恒定的,那么为什么通过擦除 O(n) 删除元素的时间复杂度。 here 这里的答案之一指出通过擦除删除元素是 O(n)。我知道擦除删除了起始迭代器和结束迭代器之间的元素,所以答案基本上意味着它的O(n) 取决于两个迭代器之间的元素数量以及从任何索引中的向量/双端队列中删除单个元素会是零吗?

【问题讨论】:

  • deque::erase 应该比vector::erase 快(对于一个普通元素),因为它只需要重新洗牌受影响的块中的元素,而不是整个vector .
  • @Walter:你确定吗?
  • @Walter 这不完全正确,请参阅我的回答和demo
  • @Mehrdad 不,我不确定,只是基于我将实现的想法(并假设只删除一个元素)。事实上,如果擦除不保留剩余元素的顺序是可能的/允许的,这可以在恒定时间内完成!为什么标准不明确支持这一点? (您可以简单地通过首先将最后一个元素与要擦除的元素交换,然后擦除最后一个元素来实现这一点,但这可能会更慢,因为交换可能比单次移动更昂贵。)
  • @Walter:嗯,我很确定这是错误的,因为否则找到两个迭代器之间的距离不会花费恒定的时间。

标签: c++ vector deque c++98


【解决方案1】:

std::vectorstd::deque 的情况有些不同,C++98 和 C++11 也不同。

std::vector

std::vector::erase() 的复杂性与擦除范围的长度以及范围末尾和容器末尾之间的元素数量呈线性关系(因此从末尾擦除一个元素需要恒定的时间)。

C++2003 [lib.vector.modifiers] 读取:

iterator erase(iterator position);
iterator erase(iterator first, iterator last);`

...

复杂度:T的析构函数被调用的次数等于被擦除的元素个数, 但是T赋值操作符被调用的次数等于被擦除元素后向量中的元素个数。

C++14 草案 N4140 [vector.modifiers] 内容:

复杂度:T的析构函数被调用的次数等于元素个数 被抹去,但是T移动赋值运算符被调用的次数等于 删除元素之后的向量中的元素。

因此您看到 C++11/14 实现通常更高效,因为它执行移动分配而不是复制分配,但复杂度保持不变。

std::deque

std::deque::erase() 的复杂性与擦除范围的长度和两个数字的最小值成线性关系:范围开始之前剩余元素的数量和剩余元素的数量范围结束后。因此,从头或尾擦除元素需要恒定的时间。

C++2003 [lib.deque.modifiers]:

iterator erase(iterator position);
iterator erase(iterator first, iterator last);

复杂性: 调用析构函数的次数与擦除的元素数相同,但 对赋值运算符的调用次数最多等于元素数量的最小值 被擦除元素之前和被擦除元素之后的元素个数。

C++14 草案 N4140 [deque.modifiers]/5:

复杂性:调用析构函数的次数与擦除的元素数量相同,但调用赋值运算符的次数不超过擦除元素之前的元素数量和擦除元素之后的元素数量中的较小者。

所以,在 C++98 和 C++11/14 中是一样的,除了 C++11 可以在移动赋值和复制赋值之间进行选择(这里我看到标准中的一些不一致,因为措辞没有没有提到像std::vector 这样的移动分配 - 可能是另一个问题的原因。

还要注意措辞中的“最多”和“不再”。这允许实现比线性更有效,尽管实际上它们是线性的 (DEMO)。

【讨论】:

    【解决方案2】:

    删除向量中的元素是 O(n),因为一旦删除元素,您仍然需要移动所有连续元素以填补创建的空白。如果一个向量有 n 个元素,那么在最坏的情况下,您需要移动 n-1 个元素,因此复杂度是 O(n)。

    【讨论】:

      【解决方案3】:

      删除元素确实是O(n) 不是因为你必须做什么才能找到要删除的元素,而是因为你必须对它之后的所有元素做些什么。这些元素需要向下滑动以填充空槽。

      所以平均而言,擦除将在向量的一半左右占用一个元素,因此您必须移动大约一半的元素。因此O(n)。最好的情况是,您擦除最后一个元素 - 无需滑动。最坏的情况是,您删除第一个元素 - 然后必须移动 每个 其他元素。

      【讨论】:

      • 这绝对是有道理的,因为向量是一个动态数组是连续的
      【解决方案4】:

      通过这种方式,时间复杂度 = 范围长度 + 移动长度(n - 范围结束)

          vector<int> it = (vector<int>::iterator) &vec[pos];
          vec.erase(it, it+length);
      

      【讨论】:

        猜你喜欢
        • 2020-01-28
        • 2017-01-24
        • 1970-01-01
        • 1970-01-01
        • 2014-03-30
        • 2016-02-26
        • 2011-09-03
        • 2012-09-07
        • 2018-06-28
        相关资源
        最近更新 更多