【问题标题】:Why is std::vector::insert complexity linear (instead of being constant)?为什么 std::vector::insert 复杂度是线性的(而不是恒定的)?
【发布时间】:2014-08-09 13:06:45
【问题描述】:

假设我在大小为“n”的std::vector<mytype> 的第“i”个位置插入了 p 个新元素。

由于std::vector 中的项目保证为其元素使用连续的存储位置,因此我似乎需要 4 个步骤来完成上述操作:

1) 如果空间不足,向量可能会重新分配,基本上是其大小的两倍。但这是一个恒定的时间操作(尽管非常大)。

2) 接下来是从旧向量到新向量的索引 0 到 i-1 的元素的 memcpy。

3) 然后你复制 'p' 个新项目被插入到第 i 个索引处。

4) 然后从旧向量到新向量的 i+1 到 n 个索引的所有项目的另一个 memcpy。

以上不都是常数时间运算吗?那么插入本身不应该是一个恒定的时间操作吗?那么为什么std::vector::insert 与插入的元素数量(复制/移动构造)加上定位后的元素数量(移动)成线性关系?

【问题讨论】:

  • 重新分配成本应该随着大小而增加。
  • 如果底层内存块没有被完全使用,则不需要重新分配,但是你应该将n个元素向左移动以插入新的元素。(这样就完成了通过memcpy() 并且,正如其他人所注意到的,它不是 O(1)。memcpy()ing 4KB 需要与 3MB 相同的时间?当然不会。)
  • 当然不是。 memcpy 的成本与复制的数据量渐近成正比。
  • 任何使用 memcpy 的 std::vector 实现都存在根本缺陷。它没有正确调用构造函数/析构函数和赋值运算符。
  • @Andrey Chernyakhovskiy:C++ 标准不要求向量类以任何特定方式管理其内存。它的作用是要求通过重复调用 push_back 创建一个 n 元素向量不超过 O(n) 时间。

标签: c++ stl


【解决方案1】:

以上不都是常数时间运算吗?

不,memcpymemmove 的时间复杂度与被复制或移动的块的大小成线性关系,因为被移动的每个 k 字节都需要恰好被触摸一次。被移动的块的大小是sizeof(T) * N,使得时间也是线性的。

即使在向量末尾添加元素也具有线性复杂性,因为在重新分配时复制数据(但是,将 N 元素添加到向量末尾具有摊销线性复杂性,这转化为每项复杂度的摊销常数)。

【讨论】:

  • 你的意思是,摊销的常数复杂性。此外,纠正非平凡复杂类型的 OP 将是一个不错的选择。
【解决方案2】:

您提到可能存在重新分配,那么在这种情况下,时间复杂度显然将是线性,如上述答案中所述。但是即使没有重新分配,时间复杂度也将是 linear 因为 vector::insert() 函数的复杂度为 O(n+m) 其中 n 是插入的元素数m 是移动的元素数

示例:假设我们在向量前面插入一个元素,那么向量中存在的所有元素都将向右移动(这需要线性时间),因此可以将元素插入到前面。时间复杂度为 O(1 + elements_in_vector)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-03
    • 2019-02-25
    • 2020-12-04
    • 2015-04-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多