In this section, we present the data structure, which implements a FIFO (first-in-first-out) queue; elements are removed (using the operation) from the queue in the same order they are added (using the operation).
Notice that an is a poor choice for an implementation of a FIFO queue. The reason is that we must choose one end of the list to add to and then remove from the other end. One of the two operations must work on the head of the list, which involves calling or with a value of . This gives a running time proportional to .
To obtain an efficient array-based implementation of a queue, we first notice that the problem would be easy if we had an infinite array . We could maintain one index that keeps track of the next element to remove and an integer that counts the number of elements in the queue. The queue elements would always be stored in
Of course, the problem with this solution is that it requires an infinite array. An simulates this by using a finite array and modular arithmetic. This is the kind of arithmetic used when we are talking about the time of day. For example 10 o'clock plus 5 hours gives 3 o'clock. Formally, we say that
More generally, for an integer and positive integer , is the unique integer such that for some integer . Less formally, the value is the remainder we get when we divide by . In many programming languages, including C++, the operator is represented using the symbol.2.2
Modular arithmetic is useful for simulating an infinite array, since always gives a value in the range . Using modular arithmetic we can store the queue elements at array locations
The only remaining thing to worry about is taking care that the number of elements in the does not exceed the size of .
array<T> a; int j; int n;
A sequence of and operations on an is illustrated in Figure 2.2. To implement , we first check if is full and, if necessary, call to increase the size of . Next, we store in and increment .
|
bool add(T x) { if (n + 1 > a.length) resize(); a[(j+n) % a.length] = x; n++; return true; }
To implement we first store so that we can return it later. Next, we decrement and increment (modulo ) by setting . Finally, we return the stored value of . If necessary, we may call to decrease the size of .
T remove() { T x = a[j]; j = (j + 1) % a.length; n--; if (a.length >= 3*n) resize(); return x; }
Finally, the operation is very similar to the operation of . It allocates a new array of size and copies
void resize() { array<T> b(max(1, 2*n)); for (int k = 0; k < n; k++) b[k] = a[(j+k)%a.length]; a = b; }
The following theorem summarizes the performance of the data structure: