There are at least two common ways to efficiently implement a deque,
that are often pitted against one another: with a doubly linked list or with a modified dynamic array. There are many variations and actual implementations are often hybrid solutions. Additionally, several
purely functional implementations of the double-ended queue exist.
Doubly linked list While a simple list may implement a deque, a
doubly-linked list is more adequate for its symmetry to achieve a fast access to both ends of the list (
head and
tail, hence the name
head-tail linked list). The obvious solution is to manage two references; alternatively the deque can be built as a circular list. In a doubly-linked list implementation, and assuming no allocation/deallocation overhead, the
time complexity of all deque operations is Big O notation|. Additionally, insertion or deletion in the middle, given an iterator, can also be achieved in constant time; however, the time taken for random access by index is . Similarly, finding a specific element normally
requires time. Linked data structures generally have poor locality of reference.
Dynamic array ), while some are slow due to the need for
reallocation ( time, labelled with turtles).In this case as well, while a
dynamic array can be used to implement a deque, a variant that can grow from both ends is more appropriate. This is sometimes called
array deques. This can be achieved in various ways, e.g.: • by offsetting the position of the first element of the array in the reserved memory: the unused space is distributed on both side of the data; • with a
circular array. The
amortized time complexity of all deque operations with an
array deque is Big O notation|, thanks to the geometric expansion of the back-end buffer. Additionally, random access by index takes constant time ; but the average time taken for insertion or deletion in the middle are . Thank to the fast random access, finding an element in an ordered array is time (
binary search). Each time the array is resized, the whole content is moved: the memory usage is then momentarily doubled (or more), and all direct (external) references to the content of the array are lost.
Functional and persistent implementations Doubly linked lists cannot be used as
immutable data structures. And an immutable array would be highly inefficient (an array is often simulated by
a tree). A purely functional implementation of the deque can be based on stack, that is easily implemented with a
singly linked list as an immutable and
persistent structure. This last process could be rather complicated as it needs to be executed concurrently with other operations and completed before the next one, to achieve an amortized real-time complexity. The next step is to support the operations in
worst-case time. Another challenge is the real-time catenation of deques.
Okasaki gives a simple solution that uses
lazy lists combined with
memoization. The stack to stack balancing is then partially automatic by means of a precise scheduling of incremental functions. •
data-structural bootstrapping, resulting in a recursive structure that foresees the
finger tree: a deque is a triple consisting of a sub-deque flanked by two size-bounded buffers.
Enqueue and
dequeue basically operate on the buffers (in real-time because of the bounded size), and move forward one step in the balancing process; •
recursive slow down, inspired by
Redundant binary representation (RBR), where an additional 2 digit stands for a suspended carry: the sub-deque contains pairs of elements from the parent deque, and the propagation of the balancing process to the sub-deque is delayed like is the carry propagation after the increment or decrement of a RBR number; More generally, real-time catenation requires a deque is a tuple consisting mainly in two sub-structures, that themselves contain deques or compounds of deques. The linear spine of the non-catenable deque is then replaced by a
binary skeleton. Kaplan, Okasaki, and Tarjan produced a simpler, amortized version that can be implemented either using lazy evaluation or more efficiently using mutation in a broader but still restricted fashion.{{cite journal |first1=Haim |last1=Kaplan |first2=Chris |last2=Okasaki |first3=Robert E. |last3=Tarjan Mihaescu and Tarjan created a simpler (but still highly complex) strictly purely functional implementation of catenable deques, and also a much simpler implementation of strictly purely functional non catenable deques, both of which have optimal worst-case bounds (not officially published).{{citation ==Language support==