DualArrayDeque: Building a Deque from Two Stacks
Learn how to implement a DualArrayDeque data structure.
We'll cover the following...
In this lesson, we present a data structure, the DualArrayDeque
, that achieves the same performance bounds as an ArrayDeque
by using two ArrayStack
s. Although the asymptotic performance of the DualArrayDeque
is no better than that of the ArrayDeque
, it is still worth studying, since it offers a good example of how to make a sophisticated data structure by combining two simpler data structures.
Two ends of a DualArrayDeque
A DualArrayDeque
represents a list using two ArrayStack
s. Recall that an ArrayStack
is fast when the operations on it modify elements near the end. A DualArrayDeque
places two ArrayStack
s, called front
and back
, back-to-back so that operations are fast at either end.
class DualArrayDeque(BaseList):def __init__(self, iterable=[]):self._initialize()self.add_all(iterable)def _initialize(self):self.front = ArrayStack()self.back = ArrayStack()
A DualArrayDeque
does not explicitly store the number, , of elements it contains. It doesn’t need to, since it contains n = front.size() + back.size()
elements. Nevertheless, when analyzing the DualArrayDeque
we will still use to denote the number of elements it contains.
class DualArrayDeque(BaseList):def __init__(self, iterable=[]):self._initialize()self.add_all(iterable)def size(self):return self.front.size() + self.back.size()
The DualArrayDeque
operations
The front
ArrayStack
stores the list elements that whose indexes are but stores them in reverse order. The back
ArrayStack
contains list elements with indexes in in the normal order. In this way, get(i)
and set(i, x)
translate into appropriate calls to get(i)
or set(i, x)
on either front
or back
, which take time per operation.
class DualArrayDeque(BaseList):def __init__(self, iterable=[]):self._initialize()self.add_all(iterable)def get(self, i):if i < self.front.size():return self.front.get(self.front.size()-i-1)else:return self.back.get(i-self.front.size())def set(self, i, x):if i < self.front.size():return self.front.set(self.front.size()-i-1, x)else:return self.back.set(i-self.front.size(), x)
Note that if an index i < front.size()
, then it corresponds to the element of front
at position front.size() − i − 1
, since the elements of front
are stored in reverse order.
Visual demonstration
Adding and removing elements from a DualArrayDeque
is illustrated below. Arrows denote elements being copied. Operations that result in a rebalancing by _balance()
are marked with an asterisk.
The add(i, x)
operation manipulates either front
or back
, as appropriate:
class DualArrayDeque(BaseList):def __init__(self, iterable=[]):self._initialize()self.add_all(iterable)def add(self, i, x):if i < self.front.size():self.front.add(self.front.size()-i, x)else:self.back.add(i-self.front.size(), x)self._balance()
The add(i, x)
method performs rebalancing of the two ArrayStack
s front
and back
, by calling the _balance()
method. The implementation of _balance()
is described below, but for now it is sufficient to know that balance()
ensures that, unless size() < 2
, front.size()
and back.size()
do not differ by more than a factor of . In particular, and .
Next we analyze the cost of add(i, x)
, ignoring the cost of calls to balance()
. If i < front.size()
, then add(i, x)
gets ...
Create a free account to access the full course.
By signing up, you agree to Educative's Terms of Service and Privacy Policy