为什么在std :: deque常量O(1)的末尾或开头插入或删除元素的复杂性?

时间:2017-10-03 09:38:51

标签: c++11

根据C++ standard std :: deque就像是

std::vector<std::array<T, M> *>

如果是这样,那么在结尾或开头插入或删除元素是如何成为常数O(1)?如果向量的容量超出并且我们在结尾或开始处插入一些东西,则无法保证整个向量不会被重新分配,因此我们得到0(N / M)实际上是0(N),isn&t; t它? (N是双端队列的大小)。

2 个答案:

答案 0 :(得分:3)

  

如果向量的容量超出并且我们在结尾或开始处插入一些东西,则无法保证整个向量不会被重新分配,因此我们得到0(N / M)实际上是0(N),isn&# 39?是吗? (N是双端队列的大小)。

是的,但是不需要复制/移动任何元素来重新分配该向量,只需要将指向数组的指针移动到新存储。

标准规定:

  

<强> [container.requirements.general]
  -2-本条款中的所有复杂性要求仅根据所包含对象的操作次数进行说明。

因此,在O(1)复杂度要求中,N / M指针副本的事实并未计算在内。由于这些指针操作非常便宜,因此这些操作的性能在实践中不是问题。 deque需要为插入的每个M元素分配一个新页面,但它永远不需要重新分配现有页面并移动每个现有元素,因此它避免了向量上最昂贵的操作,因此向量的指数不需要增长来使deques便宜。

答案 1 :(得分:2)

通过从指针向量的中间向外分配,可以(通常)实现在前面插入的分摊常数复杂度。

例如,如果我们有一个包含3个节点的双端队列,每个节点最多可容纳4个元素,那么我们可以为其分配8个指针的向量:

[ nil, nil, nil, N1*, N2*, N3*, nil, nil ]

此处N1N3可能会被部分填充:

N1: [ nil, nil, nil, 1 ]
N2: [ 2, 3, 4, 5 ]
N3: [ 6, 7, nil, nil ]

当我们push_front到双端队列时,首先向前填充N1,然后分配额外的节点并将其添加到指针向量中。一旦指针向量在前面填充,任何额外的push_front都会导致指数扩展,并在前面分配额外的空间:

[ N1*, N2*, N3*, N4*, N5*, N6*, nil, nil ]
  |                         `---------------------------------------\
  `-----------------------------------v                             v
[ nil, nil, nil, nil, nil, nil, nil, N0*, N1*, N2*, N3*, N4*, N5*, N6*, nil, nil ]

此指数增长政策实现了deque::push_frontdeque::push_back的O(1)摊销复杂性,原因与vector::push_back具有O(1)摊销复杂性的原因相同。