Why is a deque implemented with a doubly-linked list, and what are the resulting running times?
Because a deque must remove at both ends in $O(1)$, and only a doubly-linked list can find the new last node after removeLast without walking the list; with it, every deque operation runs in $O(1)$.
| Method | Running time |
|---|---|
size, isEmpty |
$O(1)$ |
first, last |
$O(1)$ |
addFirst, addLast |
$O(1)$ |
removeFirst, removeLast |
$O(1)$ |
Walk through the four modifying operations on a singly-linked list with head and tail. addFirst, removeFirst and addLast are all $O(1)$. removeLast is not: after removing the last node, tail must move to the second-to-last node, and a singly-linked node has no prev reference, so finding it means walking from the head.
The doubly-linked list fixes exactly that. The node before the trailer is trailer.prev, and the one before it is trailer.prev.prev, so removeLast becomes a constant number of reference updates. With header and trailer sentinels, all four operations are the same "insert or remove next to a sentinel" pattern:
def remove_last(self):
if self.is_empty():
raise DequeEmptyException("Deque is empty!")
last = self._trailer.get_prev()
secondtolast = last.get_prev()
self._trailer.set_prev(secondtolast)
secondtolast.set_next(self._trailer)
self._size -= 1
return last.get_element()
size and isEmpty are $O(1)$ because a counter is kept up to date, not because the list is counted.