Implementing ArrayQueue
Here is the Queue interface:
public interface Queue<T> {
void enqueue(T item); // throws IllegalArgumentException if item is null
void dequeue(); // throws NoSuchElementException if the queue is empty
T front(); // throws NoSuchElementException if the queue is empty
boolean isEmpty();
}
ArrayQueue<T> implements the Queue<T> interface with a backing array used as a circular array. There is no back field:
import java.util.NoSuchElementException;
public class ArrayQueue<T> implements Queue<T> {
private T[] arr; // the backing array, used as a circular array
private int front; // the index of the front item
private int size; // number of items in the queue
@SuppressWarnings("unchecked")
public ArrayQueue() {
arr = (T[]) new Object[10];
front = 0;
size = 0;
}
// Doubles the capacity of arr.
private void grow() { /* to implement */ }
// enqueue, dequeue, front, and isEmpty go here
}
Solution
public boolean isEmpty() {
return size == 0;
}
public T front() {
if (isEmpty()) {
throw new NoSuchElementException();
}
return arr[front];
}
public void dequeue() {
if (isEmpty()) {
throw new NoSuchElementException();
}
arr[front] = null;
front = (front + 1) % arr.length;
size--;
}
Solution
public void enqueue(T item) {
if (item == null) {
throw new IllegalArgumentException();
}
if (size == arr.length) {
grow();
}
arr[(front + size) % arr.length] = item;
size++;
}
Solution
Here is a queue state where a simple grow breaks the queue:
0 1 2 3 4
┌───┬───┬───┬───┬───┐
│ R │ S │ T │ P │ Q │
└───┴───┴───┴───┴───┘
↑
front
A full, wrapped queue: capacity 5, front is 3, and the items in order are P, Q, R, S, T at indices 3, 4, 0, 1, 2. After a simple grow, we get this:
0 1 2 3 4 5 6 7 8 9
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ R │ S │ T │ P │ Q │ _ │ _ │ _ │ _ │ _ │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
↑
front
If you were to enqueue a new item, it would be placed at index 8, since front is still 3 and size is 5, the next available index is (front + size) % arr.length = (3 + 5) % 10 = 8. The order is lost, and the queue is no longer correctly represented in the array.
If you were to dequeue from this queue, you would remove P and Q, but then the queue would appear empty even though R, S, and T are still in the array.
Solution
private void grow() {
@SuppressWarnings("unchecked")
T[] bigger = (T[]) new Object[arr.length * 2];
for (int i = 0; i < size; i++) {
bigger[i] = arr[(front + i) % arr.length];
}
arr = bigger;
front = 0;
}
Solution
isEmpty, front, and dequeue are : each reads or writes front, size, and at most one slot. enqueue is amortized : it writes one slot, and the grow runs only when the array is full, which doubling makes rare.
Solution
Both use space. The array usually has more slots than items, and it keeps its largest capacity after the queue gets smaller. LinkedQueue allocates exactly one node per item, but each node also stores a next reference.