stl deque::insert()的复杂性

Complexity of stl deque::insert()

本文关键字:复杂性 insert deque stl      更新时间:2023-10-16

我从2003年c++标准(第23.2.1.3章)中学到了deque::insert()的复杂性:

在最坏的情况下,将单个元素插入到deque所需的时间以插入点到deque开始和插入点到deque结束的距离的最小值为线性。

我总是把stl deque的实现理解为内存块的集合。因此,插入只会影响与插入位置相同的内存块中的元素。我的问题是,"从插入点到队列开始的距离的最小线性以及从插入点到队列结束的距离"的标准是什么意思?

我的理解是因为c++标准没有强制deque的某种实现。复杂度只是最坏情况下的一般情况。然而,在编译器的实际实现中,它与内存块中的元素数量是线性的,元素大小可能不同。

另一种猜测可能是,由于insert()将使所有迭代器失效,deque需要更新所有迭代器。因此它是线性的。

std::deque通常(总是)被实现为内存块的集合,但是它通常不会为你在集合中间插入一个新元素而插入一个全新的块。因此,它将发现插入点是更靠近开始还是结束,并对现有元素进行洗牌,以便在现有块中为新元素腾出空间。它只会在集合的开始或结束时添加一个新的内存块。

我想你最好有一张图表…让我们一起玩ASCII艺术吧!

deque通常是一个内存块数组,但是前后内存块都是满的。这是必要的,因为deque是一个随机访问容器,并且为了获得对任何容器的O(1)访问,您不能有无限数量的容器来读取大小:
bool size() const { return first.size + (buckets.size()- 2) * SIZE + last.size; }
T& operator[](size_t i) {
  if (i < first.size) { return first[SIZE - i]; }
  size_t const correctedIndex = i - first.size;
  return buckets[correctedIndex / SIZE][correctedIndex % SIZE];
}

由于乘法/除法,这些操作是O(1) !

在我的例子中,我假设一个内存块包含8个元素时是满的。在实践中,没有人说过大小应该是固定的,只是说所有的内部桶都应该有相同的大小。

 // Deque
 0:       ++
 1: ++++++++
 2: ++++++++
 3: ++++++++
 4: +++++

现在假设我们想在索引13处插入。它落在标有2的桶里的某个地方。我们可以考虑以下几种策略:

  • 扩展bucket 2 (only)
  • 在2之前或之后引入一个新桶,只洗牌几个元素

但是这两种策略将违反所有"内部"桶具有相同数量元素的不变性。

因此,我们剩下的就是对元素进行洗牌,要么朝开始的方向,要么朝结束的方向(哪个更便宜),在我们的例子中:
 // Deque
 0:      +++
 1: ++++++++
 2: +O++++++
 3: ++++++++
 4: +++++

注意bucket 0是如何增长的

这种洗牌意味着,在最坏的情况下,您将移动一半的元素:O(N/2)。

deque在开始或结束时都有O(1)插入,因为这只是在正确的位置添加元素或(如果桶已满)创建新桶的问题。

还有其他基于B+树的容器在随机索引上有更好的插入/擦除行为。在有索引的B+ Tree中,您可以在内部维护某个位置之前的元素计数,而不是"键"(或并行)。有各种各样的技术可以有效地做到这一点。最后你会得到一个容器:

  • 0(1):空,size
  • 0 (log N): at, insert, erase

您可以查看Python中的blist模块,它实现了由这种结构支持的类似Python列表的元素。

你的推测是…99.9%真实的。一切都取决于实际的实现是什么。标准规定的是对实现者(如果不符合规范,就不能声称是标准)和用户(如果编写独立于实现的代码,就不能期望"更好的性能")的最低要求。

规范背后的思想是一块未初始化的内存(a ==一个),其中元素在中心周围分配…直到有空间给他们。插入中间意味着移位。插入在前端或末端意味着只是在原地构建。(当没有空间存在时,进行重新分配)修改后的索引和迭代器是不可信的,因为我们不能假设什么被移动了,以及往哪个方向移动了。

更有效的实现不使用单个块,而是使用多个块来重新分配"移动"问题,并从底层系统分配恒定大小的内存(从而限制了重新分配和碎片)。如果您的目标是其中一个,您可以期望更好的性能,否则您最好不要进行任何结构优化。

与插入的元素数量成线性关系(复制构造)。另外,根据特定的库实现,从position到deque的一个端点之间的元素数量会增加额外的线性时间。参考…