Queues

Steven J. Zeil

Last modified: Oct 26, 2023
Contents:

A queue is a sequential container in which

1 std Interface

queue<string, list<string> > q;
q.push("abc");
q.push("de");
assert (q.front() == "abc");
assert (q.back() == "de");
q.pop();
assert (q.size() == 1);
assert (q.front() == "de");
q.pop();
assert (q.empty());

Here are some examples of typical queue manipulation.

(Again, all the assertions should be true.)

2 Implementing Queues

Like stacks, queues can be implemented using arrays or lists. A list-based implementation is pretty straightforward:

Notice that adding and removing elements from the queue are O(1) operations.

2.1 Implementing Queues Using Arrays

Surprisingly, an array-based implementations of queues is far more difficult than it was for stacks, because we must allow the structure to grow at one end and shrink from the other. If we add to the “end” of an array or vector, then we would be removing from the front. But that’s an $O(\mbox{size()})$ operation. We’d like to avoid that.

Here’s an array-based implementation.

It repeatedly goes through the sequence: add “Adams” to the end, then add “Baker” to the end, then add “Carter” to the end, then remove the front (“Adams”), add “Davis” to the end, then remove (“Baker”), remove (“Carter”), and remove (“Davis”), leaving us with an empty queue.

Watch carefully as we go through multiple repetitions of this sequence. Notice how, as we do this, the “useful” portion of the array, the part that actually contains the queue’s data, keeps sliding further back in the array?

What do we do when we bump into the end of the array and still want to add something to the queue? There may be lots of unused space at the front, so we should be able to squeeze the new data in somewhere!

Now, we could just copy all the remaining data back to the beginning of the array. But doing this would make adding data to the end of the queue an O(N) operation (where N is the number of elements still in the queue). For the list we managed this in O(1) time, so we might ask if we can’t do that well with arrays.

The solution to this problem is to simply let start and stop “wrap around” to the beginning of the array. For example, when adding to the queue, instead of

stop++;

we use

stop = (stop + 1) % size;

where size is the size of the array being used to hold the queue.

The % is the “modulus” operator – it returns the remainder when the first argument is divided by the second. So if stop==size-1, then size+1 equals stop, and the remainder when stop is divided by itself is 0, so (stop+1)% size == 0. Thus after stop (or start) have made it all the way to the end of the array, the next time we advance them they will “wrap around” to 0.

Here’s the actual implementation of pushing and popping:

qpush.cpp
template <class T>
void queue<T>::push (const T& x)
{
  assert (theSize < ArraySize);
  stop = (stop + 1) % ArraySize;
  array[stop] = x;
  theSize++;
}


template <class T>
inline
void queue<T>::pop()
{
  assert (theSize > 0);
  start = (start + 1) % ArraySize;
  theSize--;
}

3 Applications & Examples

Common applications of queues include:

Tree and graph processing will be explored in later sections of the course. Your text describes the use of queues in simulations. We’ll explore some I/O uses of queues in the assignments.