有一种直觉,即从数组的前面移除将需要将所有内容移回,这真是太好了。但是,在特定情况下ArrayDeque
,实现的设计方式不需要这样做。
基于数组的双端队列通常使用称为循环缓冲区的数据结构来实现。这个想法是我们维护一个元素数组,但假设数组的末端粘在一起形成一个环。
内部ArrayDeque
维护一个包含 16 个元素的数组,我们可以这样查看:
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| | | | | | | | | | | | | | | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
我们维护两个不同的指针,一个头指针和一个尾指针,跟踪双端队列第一个元素的位置和双端队列最后一个元素的位置。最初,它们将指向数组的开头:
head
|
v
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| | | | | | | | | | | | | | | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
^
|
tail
每当我们执行 anaddFirst
时,我们都会将头指针备份一步,然后将元素写入我们找到的位置。由于我们假设数组的两端是链接在一起的,所以这里后退一步会将头指针移动到最后一个位置:
head
|
v
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| | | | | | | | | | | | | | | | X |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
^
|
tail
要做一个addLast
,我们写到尾部位置,然后向前推进:
head
|
v
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| X | | | | | | | | | | | | | | | X |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
^
|
tail
如果我们再做两个 s,它会是什么样子addFirst
:
head
|
v
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| X | | | | | | | | | | | | | X | X | X |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
^
|
tail
addLast
这就是如果我们再做两个s 的样子:
head
|
v
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| X | X | X | | | | | | | | | | | X | X | X |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
^
|
tail
我们从头指针开始读取双端队列的元素,然后继续前进,直到到达尾指针。所以在这种情况下,我们从 指向的槽开始读取head
,而不是数组中的第一个位置。
以这种方式安排事情使得删除双端队列的第一个元素非常快(O(1))。我们只是清除了指向的入口head
,向前head
迈了一步,像这样:
head
|
v
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| X | X | X | | | | | | | | | | | | X | X |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
^
|
tail
请注意,没有什么需要移动,因为我们只是假装我们从数组中的较晚位置开始。
但是,如果您要从ArrayDeque
. 但是,再一次,ArrayDeque
没有针对该用例进行优化;顾名思义,它专门设计为双端队列。