Next, we present another data structure, the
that
achieves the same performance bounds as an
by using
two
s. Although the asymptotic performance of the
is no better than that of the
, 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.
A
represents a list using two
s. Recall that
an
is fast when the operations on it modify elements near
the end. A
places two
s, called
and
, back-to-back so that operations are fast at either end.
ArrayStack<T> front; ArrayStack<T> back;
A
does not explicitly store the number,
,
of elements it contains. It doesn't need to, since it contains
elements. Nevertheless, when
analyzing the
we will still use
to denote the number
of elements it contains.
int size() { return front.size() + back.size(); }
The
contains list elements with indices
, but stores them in reverse order.
The
contains list elements with indices
in the normal order. In this way,
and
translate into appropriate calls to
or
on either
or
, which take
time per operation.
T get(int i) { if (i < front.size()) { return front.get(front.size() - i - 1); } else { return back.get(i - front.size()); } } T set(int i, T x) { if (i < front.size()) { return front.set(front.size() - i - 1, x); } else { return back.set(i - front.size(), x); } }
Note that, if an index
, then it corresponds to the
element of
at position
, since the
elements of
are stored in reverse order.
Adding and removing elements from a
is illustrated in
Figure 2.4. The
operation manipulates either
or
, as appropriate:
![]() |
void add(int i, T x) { if (i < front.size()) { front.add(front.size() - i, x); } else { back.add(i - front.size(), x); } balance(); }
The
method performs rebalancing of the two
s
and
, by calling the
method. The
implementation of
is described below, but for now it is
sufficient to know that
ensures that, unless
,
and
do not differ by more than a factor
of 3. In particular,
and
.
Next we analyze the cost of
, ignoring the cost of the
operation. If
, then
becomes
.
Since
is an
, the cost of this is
Notice that the first case (2.1) occurs when
.
The second case (2.2) occurs when
. When
, we can't be sure whether the operation affects
or
, but in either case, the operation takes
time, since
and
. Summarizing the situation, we have
The
operation, and its analysis, is similar to the
operation.
T remove(int i) { T x; if (i < front.size()) { x = front.remove(front.size()-i-1); } else { x = back.remove(i-front.size()); } balance(); return x; }
Finally, we study the
operation performed by
and
. This operation is used to ensure that neither
nor
gets too big (or too small). It ensures that, unless there
are fewer than 2 elements, each of
and
contain at least
elements. If this is not the case, then it moves elements between
them so that
and
contain exactly
elements and
elements, respectively.
void balance() { if (3*front.size() < back.size() || 3*back.size() < front.size()) { int n = front.size() + back.size(); int nf = n/2; array<T> af(max(2*nf, 1)); for (int i = 0; i < nf; i++) { af[nf-i-1] = get(i); } int nb = n - nf; array<T> ab(max(2*nb, 1)); for (int i = 0; i < nb; i++) { ab[i] = get(nf+i); } front.a = af; front.n = nf; back.a = ab; back.n = nb; } }
There is not much to analyze. If the
operation does
rebalancing, then it moves
elements and this takes
time. This is bad, since
is called with each call to
and
. However, the following lemma shows that, on
average,
only spends a constant amount of time per operation.
We will perform our analysis using the potential method.
Define the potential of the
as
Observe that, immediately after a call to
that shifts
elements, the potential,
, is at most 1, since
Consider the situation immediately before a call to
that
shifts elements and suppose, without loss of generality, that
is shifting elements because
.
Notice that, in this case,
![]() |
![]() |
![]() |
|
![]() |
![]() |
||
![]() |
![]() |
![]() |
![]() |
![]() |
|
![]() |
![]() |
||
![]() |
![]() |
||
![]() |
![]() |
||
![]() |
![]() |
The following theorem summarizes the performance of a
opendatastructures.org