An 
 implements the list interface using an array 
, called
the backing array.  The list element with index 
 is stored
in 
.  At most times, 
 is larger than strictly necessary,
so an integer 
 is used to keep track of the number of elements
actually stored in 
.  In this way, the list elements are stored in
,...,
 and, at all times, 
.
  array<T> a;
  int n;
  int size() {
    return n;
  }
Accessing and modifying the elements of an 
 using 
and 
 is trivial. After performing any necessary bounds-checking
we simply return or set, respectively, 
.
  T get(int i) {
    return a[i];
  }
  T set(int i, T x) {
    T y = a[i];
    a[i] = x;
    return y;
  }
The operations of adding and removing elements from an 
are illustrated in Figure 2.1.  To implement the 
operation, we first check if 
 is already full.  If so, we call
the method 
 to increase the size of 
.  How 
is implemented will be discussed later.  For now, it is sufficient to
know that, after a call to 
, we can be sure that 
.  With this out of the way, we now shift the elements
 right by one position to make room for 
,
set 
 equal to 
, and increment 
.
 
  
 | 
  void add(int i, T x) {
    if (n + 1 > a.length) resize();
    for (int j = n; j > i; j--)
      a[j] = a[j - 1];
    a[i] = x;
    n++;
  }
If we ignore the cost of the potential call to 
Implementing the 
 operation is similar.  We shift the elements
 left by one position (overwriting 
) and
decrease the value of 
.  After doing this, we check if 
 is getting
much smaller than 
 by checking if 
. If so,
we call 
 to reduce the size of 
.
  T remove(int i) {
      T x = a[i];
    for (int j = i; j < n - 1; j++)
      a[j] = a[j + 1];
    n--;
    if (a.length >= 3 * n) resize();
    return x;
  }
If we ignore the cost of the 
The 
 method is fairly straightforward; it allocates a new
array 
 whose size is 
 and copies the 
 elements of 
 into
the first 
 positions in 
, and then sets 
 to 
. Thus, after a call to 
, 
.
  void resize() {
    array<T> b(max(2 * n, 1));
    for (int i = 0; i < n; i++)
      b[i] = a[i];
    a = b;
  }
Analyzing the actual cost of the 
 operation is easy. It
allocates an array 
 of size 
 and copies the 
 elements of 
into 
.  This takes 
 time.
The running time analysis from the previous section ignored the cost
of calls to 
.  In this section we analyze this cost using a
technique known as amortized analysis.  This technique does not
try to determine the cost of resizing during each individual 
and 
 operation.  Instead, it considers the cost of all calls to
 during a sequence of 
 calls to 
 or 
.
In particular, we will show:
There are two cases to consider. In the first case, 
 is
  being called by 
 because the backing array 
 is full, i.e.,
  
.  Consider the previous call to 
:
  After this previous call, the size of 
 was 
, but the
  number of elements stored in 
 was at most 
.
  But now the number of elements stored in 
 is 
,
  so there must have been at least 
 calls to 
 since
  the previous call to 
.
  
The second case to consider is when 
 is being called by
  
 because 
.  Again, after the
  previous call to 
 the number of elements stored in 
 was
  at least 
.1 Now there
  are 
 elements stored in 
.  Therefore, the number
  of 
 operations since the last call to 
 is at least
  
The following theorem summarizes the performance of an 
:
The 
 is an efficient way to implement a 
.
In particular, we can implement 
 as 
 and 
as 
, in which case these operations will run in 
amortized time.