根据C++ standard std :: deque就像是
std::vector<std::array<T, M> *>
如果是这样,那么在结尾或开头插入或删除元素是如何成为常数O(1)?如果向量的容量超出并且我们在结尾或开始处插入一些东西,则无法保证整个向量不会被重新分配,因此我们得到0(N / M)实际上是0(N),isn&t; t它? (N是双端队列的大小)。
答案 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 ]
此处N1
和N3
可能会被部分填充:
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_front
和deque::push_back
的O(1)摊销复杂性,原因与vector::push_back
具有O(1)摊销复杂性的原因相同。