我认为图表会更好地为您服务……让我们来玩 ASCII 艺术吧!
一个双端队列通常是一个内存块数组,但除了前内存块和后内存块外,其他所有内存块都是满的。这是必要的,因为双端队列是一个 RandomAccessContainer,并且要获得对任何容器的 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 的存储桶中的某个位置。我们可以考虑以下几种策略:
- 扩展存储桶 2(仅)
- 在 2 之前或之后引入一个新的桶,并且只打乱几个元素
但是这两种策略会违反所有“内部”存储桶具有相同数量的元素的不变量。
因此,在我们的例子中,我们只能在开始或结束(以更便宜的那个为准)周围改组元素:
// Deque
0: +++
1: ++++++++
2: +O++++++
3: ++++++++
4: +++++
请注意存储桶 0 是如何增长的。
这种洗牌意味着,在最坏的情况下,您将移动一半的元素:O(N/2)。
deque
虽然在开头或结尾都有 O(1) 插入,因为只需在正确的位置添加元素或(如果桶已满)创建一个新桶。
还有其他容器在随机索引处具有更好的插入/擦除行为,基于B+ Trees。在索引 B+ 树中,您可以代替“键”(或并行)在内部维护某个位置之前的元素计数。有多种技术可以有效地做到这一点。最后你会得到一个容器:
- O(1):空,大小
- O(log N):在、插入、擦除
您可以检查blist
Python 中的模块,该模块实现了由这种结构支持的类似 Python 列表的元素。