Dynamic ArrayStack
An array implementation of Stack can be constructed by allocating an array of fixed-size, typically larger than the number of elements immediately required. Elements can be pushed/popped at the end of the array in constant time, until this space is completely consumed.
What should happen then?
-
Well, when the stack is full, and a client invokes "push", we can throw an exception to signal there is no space left.
-
Alternatively, we can grow the underlying array. When the underlying fixed-sized array needs to be increased in size, we can allocate a new underlying array and copy each element from the original array.
Consider the following implementation of push
. Assume the array data
has the capacity
of 10 elements.
@Override
public void push(T value) {
data[numElements++] = value;
if (numElements == capacity) {
grow();
}
}
In the code above, numElement
is the logical size of the array whereas the capacity
is its actual physical size.
Exercise Complete the implementation of the helper method grow
. The new capacity must be double the old one.
private void grow() {
// TODO: Implement me
}
Solution
In the following, we double the capacity each time growing the array.
private void grow() {
capacity *= 2;
T[] tmp = (T[]) new Object[capacity];
for (int i = 0; i < numElements; i++) {
tmp[i] = data[i];
}
data = tmp;
}
Exercise What is the complexity of the grow()
operation?
Solution
grow()
runtime (and required auxiliary space) is $O(n)$ where $n$ is the number of element (stored in the stack).
Aside: In the aforementioned implementation of push
, I have preemptively grew the array; increasing the capacity was not needed yet (not until the next push
). It can be argued that a better approach is to grow the array reactivity when a push
is requested and the capacity is full.
Resources
- Wikipedia's entry on Dynamic Array.
- InterviewCake entry on Dynamic Array.