# 2: Array-Based Lists

- Page ID
- 8436

In this chapter, we will study implementations of the `List` and `Queue` interfaces where the underlying data is stored in an array, called the backing array. The following table summarizes the running times of operations for the data structures presented in this chapter:

\(\mathtt{get(i)}\)/ \(\mathtt{set(i,x)}\) | \(\mathtt{add(i,x)}\)/ \(\mathtt{remove(i)}\) | |
---|---|---|

ArrayStack |
\(O(1)\) | \(O(\mathtt{n}-\mathtt{i})\) |

ArrayDeque |
\(O(1)\) | \(O(\min\{\mathtt{i},\mathtt{n}-\mathtt{i}\})\) |

DualArrayDeque |
\(O(1)\) | \(O(\min\{\mathtt{i},\mathtt{n}-\mathtt{i}\})\) |

RootishArrayStack |
\(O(1)\) | \(O(\mathtt{n}-\mathtt{i})\) |

Data structures that work by storing data in a single array have many advantages and limitations in common:

- Arrays offer constant time access to any value in the array. This is what allows \(\mathtt{get(i)}\) and \(\mathtt{set(i,x)}\) to run in constant time.
- Arrays are not very dynamic. Adding or removing an element near the middle of a list means that a large number of elements in the array need to be shifted to make room for the newly added element or to fill in the gap created by the deleted element. This is why the operations \(\mathtt{add(i,x)}\) and \(\mathtt{remove(i)}\) have running times that depend on \(\mathtt{n}\) and \(\mathtt{i}\).
- Arrays cannot expand or shrink. When the number of elements in the data structure exceeds the size of the backing array, a new array needs to be allocated and the data from the old array needs to be copied into the new array. This is an expensive operation.

The third point is important. The running times cited in the table above do not include the cost associated with growing and shrinking the backing array. We will see that, if carefully managed, the cost of growing and shrinking the backing array does not add much to the cost of an average operation. More precisely, if we start with an empty data structure, and perform any sequence of \(m\) \(\mathtt{add(i,x)}\) or \(\mathtt{remove(i)}\) operations, then the total cost of growing and shrinking the backing array, over the entire sequence of \(m\) operations is \(O(m)\). Although some individual operations are more expensive, the amortized cost, when amortized over all \(m\) operations, is only \(O(1)\) per operation.