Queues

Another classic data structure is the queue. A queue differs from a stack in the way its removal operation behaves---a removal removes and returns the element that has resided in the queue for the longest period of time. For this reason, a queue is called a ``first in, first out'' data structure.

It is helpful to think of a queue as a ``pipeline'', where objects enter from the rear, wait in line, and exit from the front:

               +------------------------+
   enter --->  )   )    )    )    )     ) --->  exit
               +------------------------+

Queues for resource management

Queues are used when multiple objects wish to use a ``resource,'' but only one object can use the resource at a time. A real-life example of a queue is the line of people who wait at the bank to interact with a teller---one person deals with the teller (the ``resource''), and the others order themselves so that the person who has waited the longest goes next.

A computer's operating system uses queue data structures to manage the computer's resources. Here are some examples:

Without queues, an operating system could not operate!

Queues for simulations

A classic programming application that uses queues is a simulation, where a real-life activity is modelled by objects. For example, if we wanted to simulate the airplane traffic at an airport, we use an object to model the airport's runway---this is the ``resource'' used by airplanes to take off and land, and at most one airplane can use the resource at a time. The airplanes are objects; an airplane resides either in the air, waiting to land, or on the ground, waiting to leave. The simulation places the airplanes waiting to land within a ``landing queue'' and the airplanes waiting to leave in a ``take-off queue,'' producing a situation like that in the following drawing:

           Landing queue
           ----------------------------
Plane ---> )Plane ) Plane ) ... ) Plane) ---+
           -----------------------------     \ lands
                                              \
                                               v
                                            Runway ---> (exits simulation)
                                               ^
                   Take-off queue             / leaves
                   ---------------------     /
        Plane ---> )Plane ) ... ) Plane)----+
                   ---------------------
The simulation's controller creates planes that enter the simulation, either from the air or from the ground, and it inserts each plane into the appropriate queue. When the runway is unused, the controller removes a plane from a queue and gives it the resource. When the plane completes its use of the resource, the controller removes the plane from the simulation and counts it in the simulation's ``throughput.''

Such a simulation can be used with various algorithms for using the runway to see which algorithm produces maximum ``throughput'' of planes.

Queues for communication and manufacturing

Queues often participate directly in computation, especially in manufacturing. Consider a ``print shop'', which must ``manufacture'' lines of colored text, from inputs of text and colors: From one input source (a ``producer'') a sequence of text lines is received; from a second input source, a sequence of color selections is received. The print shops matches each text line to each color and manufactures a line of colored text:

PRODUCER OF      +-----------+
TEXT LINES  ---> )   )   )   ) ---> --+  
                 +-----------+        |
                                    PRINT SHOP ---> sequence of
PRODUCER OF      +-----------+        |              colored text
COLORS    -----> )   )   )   ) ---> --+               lines 
                 +-----------+
The text lines and colors arrive at different rates, so they are entered and saved in queues. The print shop extracts a text line from one queue and a color from the other queue and combines them into a line of colored text, which it prints.

At www.cis.ksu.edu/~schmidt/300f04/Lectures/QueueEx is a small demo program that implements a simple version of the print-shop ``game.'' The program is a bit simplistic in that it asks two humans (and not two other computer processes) to be the producers of the text and colors, but the program illustrates well the importance of queues to the print shop.

The above example also suggests how queues can be used to pass messages (communications) from one agent to another in a system. Agents can read and write messages at different speeds, and the queues ensure that no messages are lost.

Defining and coding a queue

Table 9 specifies a queue data structure.

TABLE 9: specification of a queue====================================
Queue collection of objects such that the longest residing object is the only one that can be retrieved or removed.
enqueue(Object v) inserts v at the ``end'' of the queue
dequeue(): Object removes from the ``front'' of the queue the longest residing object (of the ones contained in the structure) and returns it. If the queue has no elements, an exception is thrown.
getFront(): Object returns the identity of the object at the ``front'' of the queue but does not remove it. If the queue has no elements, an exception is thrown.
isEmpty(): boolean returns whether the queue holds any elements.
ENDTABLE=========================================================

Figure 10 shows an implementation of a queue that uses a linked list.

FIGURE 10: implementation of a queue====================================

/** Queue implements a queue */
public class Queue
{ private Cell front;  // marks the first Cell of the queue
  private Cell rear;   // marks the last Cell of the queue

  /** Constructor Queue creates an empty queue */
  public Queue()
  { front = null;
    rear = null;
  }

  /** enqueue  inserts a new element to the end of the queue
    * @param ob - the element to be added */
  public void enqueue(Object ob)
  { Cell c = new Cell(ob, null);
    if ( front == null )  // is the queue empty?
       { front = c; }     // if true, then set front to  c
    else { rear.setNext(c); }  // else, attach  c  to the rear of the queue
    rear = c;
  }

 /** dequeue  removes the element from the front of the queue
   * @return the element removed
   * @exception RuntimeException if the queue is empty */
  public Object dequeue()
  { if ( front == null )
       { throw new RuntimeException("Queue error: queue empty"); }
    Object answer = front.getVal();
    if ( front == rear )  // queue contains just this one element?
       { rear = null; }   // if true, then set queue to empty
    front = front.getNext();
    return answer;
  }

 /** getFront returns the identity of the queue's front element
   * @return the element
   * @exception RuntimeException if stack is empty */
  public Object getFront()
  { if ( front == null )
       { throw new RuntimeException("Queue error: queue empty"); }
    return front.getVal();
  }

  /** isEmpty  states whether the queue has 0 elements.
    * @return whether the stack has no elements  */
  public boolean isEmpty()
  { return (front == null); }
}

ENDFIGURE================================================================
We create a queue, q, and add the strings "a", "b", and "c" to it by stating,
Queue q = new Queue();
q.enqueue("a");
q.enqueue("b");
q.enqueue("c");
At this point, element "a" resides at the front of the queue, and "c" rests at the rear. The configuration in storage looks like this:
           ----
Queue q ==| a1 |
           ----
 a1 : Queue
 -------------------
 | Cell front ==| a2 |
 |               -----
 | Cell rear ==| a4 |
 |  ...         ----

  a2 : Cell                a3 : Cell                   a4 : Cell
 ------------              ------------               ------------
 | Object val ==| "a" |    | Object val ==| "b" |    | Object val ==| "c" |
 |               -----     |               -----     |               -----
 | Cell next ==| a3 |      | Cell next ==| a4 |      | Cell next ==| null |
 |              ------     |              -----      |              -----
 |  ...                    |  ...                    | ...
At this point the message, q.dequeue(), would return "a" and reset front to a3.

Iterative operations on linked-list implementations of queues

The most elegant implementation of a queue is with a linked list of cells (although with some effort, you can use arrays to implement queues). The links in the cells suggest a natural order for examining the queue's contents--from front to rear. For practice, here is an additional method, lengthOf, that we might insert into class Queue:
/** lengthOf returns the number of objects held in the queue
  * @return the length  */
public int lengthOf()
{ int length = 0;
  Cell current = front;
  while ( current != null )
        // invariant: length holds the number of cells from the queue's
        //   front up to, but not including, the  current  one
       { length = length + 1;
         current = current.getNext();
       }
  return length;
}
Although there is another, simpler way of maintaining and returning a queue's length (hint: declare another private field, private int length), the point of the lengthOf coding is that there is a standard pattern for traversing and processing the elements of a linked list: It uses a loop and a variable, current, that examines the cells in the list one by one.

Here is another queue method:

/** toString  calculates a string representation the queue's contents.
  * @return the string  */
public String toString()
{ String answer = ""
  Cell current = front;
  while ( current != null )
        // invariant:  answer contains the string representations of the
        //  objects from the front cell up to, non including,  current
        { answer = answer + " " + current.getVal().toString();
          current = current.getNext();
        }
  return answer;
}
Again, the same pattern of loop traverses the list's cells. This pattern is a classic one and is well worth remembering.

Coding a queue with an array

It is possible to build a queue with an array. Here are some pictures to give the idea: We configure the queue with two integer variables, front and rear, and an array. An empty queue is implemented something like this:
.           +-----+
int front : |  -1 |
.           +-----+
int rear :  |  -1 |
.           +-----+
.                 0      1      2      3
.             +------+------+------+------+
Object[] q :  | null | null | null | null |
.             +------+------+------+------+
Say that objects a, b, and c are enqueued. The array fills up like this:
.           +-----+
int front : |  0  |
.           +-----+
int rear :  |   2 |
.           +-----+
.                 0      1      2      3
.             +------+------+------+------+
Object[] q :  |   a  |    b |    c | null |
.             +------+------+------+------+
Next, say that a single dequeue operation is performed. The queue's internal structure looks like this:
.           +-----+
int front : |  1  |
.           +-----+
int rear :  |   2 |
.           +-----+
.                 0      1      2      3
.             +------+------+------+------+
Object[] q :  | null |    b |    c | null |
.             +------+------+------+------+
Now, say that two more objects, d and e are enqueued. In this situation, there is a ``wrap-around'' of the array:
.           +-----+
int front : |  1  |
.           +-----+
int rear :  |   0 |
.           +-----+
.                 0      1      2      3
.             +------+------+------+------+
Object[] q :  |   e  |    b |    c |   d  |
.             +------+------+------+------+
At this point, a dequeue operation would extract the object at array cell, 1. An enqueue operation would cause an error, and indeed, this is the main disadvantage of using an array to code a queue --- the queue can fill up.

As an exercise, you should code a version of class Queue that uses an array. Take care to monitor when the queue is completely full, when the queue holds exactly one object, and when it is empty. (Do this by checking the values of front and rear.)

Queues facilitate breadth-first search

A queue can be used to conduct a breadth-first search of a tree, where the nodes inside a tree are examined in the order of closeness to the tree's root (START) node. Recall, from the lecture on stacks, that the search tree in Figure 5 listed the choices for generating permutations of four letters, and a stack was used in Figure 6 to construct a depth-first search. Figure 11 shows how to use a queue for a breadth-first search of the same tree. The front of the queue is portrayed at the left, and the queue's rear is shown at the right.

FIGURE 11: breadth-first search=========================================

Current node   Queue (front to rear)

START          (empty)

a              b c d    (examine 'a'; save 'b', 'c', 'd')

b              c d ab ac ad  (save the nodes underneath 'a' and examine 'b')

c              d ab ac ad ba bc bd  (again, save the nodes under 'b')

d              ab ac ad ba bc bd ca cb cd

ab             ac ad ba bc bd ca cb cd da db dc

ac             ad ba bc bd ca cb cd da db dc abc abd

ad             ba bc bd ca cb cd da db dc abc abd acb acd

ba             bc bd ca cb cd da db dc abc abd acb acd adb adc  

etc.

ENDFIGURE==============================================================

The traversal moves quickly across the breadth of the tree, saving lower-depth nodes for later examination. The queue ensures that the node that is removed for next examination is at the same breadth, whenever possible, as the node just examined. Contrast Figure 11 with Figure 6, whose stack delivers a node at a lower depth whenever possible.

Depth-first search proves useful for computer players of games where there are many possible next moves and many possible combinations of moves (e.g., chess). Such a situation makes it infeasible to consider all possible outcomes of all possible sequences of moves, and a (partial) breadth-first search calculates all partial sequences of moves, instead. For example, if the search tree in Figure 5 listed the sequences of moves in a ``spelling game,'' then the sixth configuration in Figure 11 holds all possible two-move sequences. At this point, the search could be stopped, and the queue examined for the best possible move based on this partial knowledge of the entire tree.

Demonstration program

Here is the two-player colors/text game that uses queues that was demonstrated in the lecture:

www.cis.ksu.edu/~schmidt/300f04/Lectures/QueueEx

The game used two queues---one held Player 1's submitted lines of text and the other held Player 2's submitted sequence of colors. The queues were constructed from class Queue, which appears in package Queue in the above game.