CS240 -- Lecture Notes: Queues

Daisy Tang

Back To Lectures Notes


The Queue Abstract Data Type

In providing services in Computer Science, transport, operations research, a queue is a buffer data structure where various entities such as data, objects, persons, or events are stored and waiting to be processed. The most well-known operations of queues: FIFO. There are also non-FIFO queue data structures, like a priority queue, where an element is added or removed based on its priority. Theoretically, one characteristics of a queue is that it does not have a specific capacity. Regardless of how many elements are already contained, a new element can always be added. 

What are the features of a queue?

  • A linear data structure with two access points: front and rear

  • Items can only be inserted to the rear (enqueue) and retrieved from the front (dequeue)

  • Dynamic length

  • The First-In, First-Out rule (FIFO)

Formally, the queue abstract data type defines a collection that keeps objects in a sequence, where element access and deletion are restricted to the first element in the sequence, which is called the front of the queue, and element insertion is restricted to the end of the sequence, which is called the rear of the queue. This restriction enforces the FIFO rule. A Java interface for the queue ADT is given below. 

public interface Queue<E> {
  /**
  * Returns the number of elements in the queue.
  * @return number of elements in the queue.
  */

  public int size();
  /**
  * Returns whether the queue is empty.
  * @return true if the queue is empty, false otherwise.
  */

  public boolean isEmpty();
  /**
  * Inspects the element at the front of the queue.
  * @return element at the front of the queue.
  */

  public E front();
  /**
  * Inserts an element at the rear of the queue.
  * @param element new element to be inserted.
  */

  public void enqueue (E element);
  /**
  * Removes the element at the front of the queue.
  * @return element removed.
  */

  public E dequeue();
}

Stores, reservation centers, and other similar services typically process customer requests according to the FIFO principle. It would be a natural choice for handling calls to the reservation center of an airline or to the box office of a theater. In operating systems, queues can be used in many places to handle multiple jobs.

 

Queue Implementation

There are many possible ways to implement the queue ADT:

  • An array with front at index 0

  • An array with floating front and rear (wrap around array)

  • A singly linked list with front

  • A singly linked list with front and rear

  • A doubly linked list

The following two programs implement the Queue ADT using different data structures. Think about the running time for each method.

 
/**
 * Implementation of the queue ADT using a fixed-length array with floating front and rear.
 **/

public class ArrQueue<D> {
  int capacity; // the capacity of the queue
  public static final int CAPACITY = 1000; // default capacity
  D Queue[]; // Generic array used to implement the queue
  int front = 0;
  int rear = 0;

  public ArrQueue() {
    this(CAPACITY);
  }

  public ArrQueue(int cap) {
    capacity = cap;
    Queue = (D[]) new Object[capacity]; // compiler may give warning, but this is ok
  }

  // returns whether the queue is full
  public boolean isFull() {
    return (Queue[front] != null && front == rear);
  }

  // returns whether the queue is empty
  public boolean isEmpty() {
    return (Queue[front] == null);
  }

  // returns the number of elements in the queue
  public int size() {
    if (isFull()) return capacity;
    else return ((rear - front + capacity) % capacity);
  }

  // Inspects and returns the element at the front of the queue
  public D front(){
    if (isEmpty()) {
        System.out.println("Queue is empty.");
        return null;
    }
    return Queue[front];
  }

  // Inserts and element at the rear of the queue
  public void enqueue(D element) {
    if (size() == capacity) {
       System.out.println("Queue is full.");
       return;
    }
    Queue[rear] = element;
    rear = (rear + 1) % capacity;
  }
  
  // Removes the element at the front of the queue
  public D dequeue() {
    if (isEmpty()) {
      System.out.println("Queue is empty.");
      return null;
    }
    D element;
    element = Queue[front];
    Queue[front] = null;
    front = (front + 1) % capacity;
    return element;
  }
  
  public String toString() {
    String s;
    int i, size = size();
    s = "["; 
    if (size > 0) s += Queue[front];
    if (size > 1) {
      i = (front + 1) % capacity;
      while ( i != rear) {
        s += ", " + Queue[i];
        i = (i + 1) % capacity;
      }
    }
    return s + "]";
  }

  public void status(String op, Object element) {
    System.out.print("----> " + op);
    if (element != null)
      System.out.print(", returns " + element);
    System.out.println(" size: " + size() + " front: " + front + " rear: " + rear);
  }
  
  public static void main(String[] args) {

    ArrQueue<String> B = new ArrQueue<String>();
    B.enqueue("Tom");
    B.enqueue("Jerry");
    B.enqueue("Jack");
    B.enqueue("Rabbit");
    System.out.println(B.toString());

    ArrQueue<Integer> A = new ArrQueue<Integer>(4);

    A.enqueue(7);
    A.status("A.enqueue(7)", null);
    System.out.println(A.toString());

    A.enqueue(9);
    A.status("A.enqueue(9)", null);
    System.out.println(A.toString());

    A.enqueue(11);
    A.status("A.enqueue(11)", null);
    System.out.println(A.toString());

    Object o;
    o = A.dequeue();
    A.status("A.dequeue()", o);
    System.out.println(A.toString());

    A.enqueue(5);
    A.status("A.enqueue(5)", null);
    System.out.println(A.toString());

    o = A.dequeue();
    A.status("A.dequeue()", o);
    System.out.println(A.toString());

    A.enqueue(8);
    A.status("A.enqueue(8)", null);
    System.out.println(A.toString());

    A.enqueue(9);
    A.status("A.enqueue(9)", null);
    System.out.println(A.toString());

    A.enqueue(2);
    A.status("A.enqueue(2)", null);
    System.out.println(A.toString());
  }
}
 
/**
 * Implementation of the queue ADT using a singly linked list with no dummy head, but with front and rear
 **/

public class ListQueue<D> {

  // the node class
  public class Node<D> {
    private D element;
    private Node<D> next;

    public Node(D e) {
      element = e;
    }

    public Node(D e, Node<D> n) {
      element = e;
      next = n;
    }
  }

  protected Node<D> front; // reference to the head node
  protected Node<D> rear; // reference to the tail node
  protected int size;    // number of elements in the queue

  public ListQueue() {
    front = null;
    rear = null;
    size = 0;
  }
  
  // returns whether the queue is empty
  public boolean isEmpty() {
    return (size == 0);
  }

  // returns the number of elements in the queue
  public int size() {
    return size;
  } 

  // Inspects and returns the element at the front of the queue
  public D front() {
    if (isEmpty()) {
        System.out.println("Queue is empty.");
        return null;
    }
    return front.element;
  } 
    
  // Inserts and element at the rear of the queue
  public void enqueue(D element) {
    Node<D> v = new Node<D>(element, null); 
    if (size = = 0) front = v;
    else rear.next = v;
    rear = v;
    size++;
  }

  // Removes the element at the front of the queue
  public D dequeue() {
    if (isEmpty()) {
      System.out.println("Queue is empty.");
      return null;
    }
    D element = front.element;
    front = front.next;
    size--;
    if (size = = 0) rear = null;
    return element;
  }

  public String toString() {
    String s;
    Node<D> ptr = front;
    s = "[";
    while (ptr != null) {
      s += ptr.element + " ";
      ptr = ptr.next;
    }
    return s + "]";
  }

  public void status(String op, Object element) {
    System.out.print("----> " + op);
    if (element != null)
      System.out.print(", returns " + element);
    System.out.println(" size: " + size);
  }

  public static void main(String[] args) {
    Object o;
    ListQueue<Integer> A = new ListQueue<Integer>();

    A.enqueue(7);
    A.status("A.enqueue(7)", null);
    System.out.println(A.toString());

    A.enqueue(9);
    A.status("A.enqueue(9)", null);
    System.out.println(A.toString());

    A.enqueue(11);
    A.status("A.enqueue(11)", null);
    System.out.println(A.toString());

    o = A.dequeue();
    A.status("A.dequeue()", o);
    System.out.println(A.toString());

    A.enqueue(5);
    A.status("A.enqueue(5)", null);
    System.out.println(A.toString());

    o = A.dequeue();
    A.status("A.dequeue()", o);
    System.out.println(A.toString());

    A.enqueue(8);
    A.status("A.enqueue(8)", null);
    System.out.println(A.toString());

    A.enqueue(9);
    A.status("A.enqueue(9)", null);
    System.out.println(A.toString());

    A.enqueue(2);
    A.status("A.enqueue(2)", null);
    System.out.println(A.toString());
  }
}
 
Queue Applications

A queue is a natural data structure for a system to serve its incoming requests. Most of the process scheduling or disk scheduling algorithms in the operating system use queues. Can we switch to stack?

Round Robin Scheduler

A popular use of the queue data structure is the scheduling problem in the operating system.  Round-robin is one of the simplest scheduling algorithms for processes in an operating system, which assigns time slices to each process in equal portions and in order, handling all processes without priority. It is starvation-free since each process gets an equal amount of time to run.

We can implement a round robin scheduler using a queue, Q, by repeatedly performing the following steps:

  1. e <-- Q.dequeue()

  2. Service process e for some time

  3. Q.enqueue(e)

The Josephus Problem

There are n people standing in a circle waiting to be executed. After the first man is executed, k - 1 people are skipped and the k-th man is executed. Then again, k-1 people are skipped and the k-th man is executed. The elimination proceeds around the circle (which is becoming smaller and smaller as the executed people are removed), until only the last man remains, who is given freedom. The task is to choose the place in the initial circle so that you survive, given n and k

// using the linked list implementation of queue, we have the following program
public class Josephus {

  // solution of the Josephus problem using a queue
  public static <D> D Josephus(ListQueue<D> Q, int k) {
    if (Q.isEmpty()) return null;
    while (Q.size() > 1) {
      System.out.println(" Queue: " + Q + " k = " + k);
      for (int i = 1; i < k; i++)
        Q.enqueue(Q.dequeue());
      D e = Q.dequeue();
      System.out.println(" " + e + " is out");
    }
    return Q.dequeue();
  }

  // build a queue from an array of objects
  public static ListQueue<D> buildQueue(D str[]) {
    ListQueue<D> Q = new ListQueue<D>();
    for (int i = 0; i < str.length; i++)
      Q.enqueue(str[i]);

    return Q;
  }

  public static void main(String[] args) {
    String[] a1 = {"Alice", "Bob", "Cindy", "Doug", "Ed", "Fred"};
    String[] a2 = {"Gene", "Hope", "Irene", "Jack", "Kim", "Lance"};
    System.out.println("First winner is " + Josephus(buildQueue(a1), 3));
    System.out.println("Second winner is " + Josephus(buildQueue(a2), 6));
  }
}

Here is the output:

intranet (55) % java Josephus
Queue: [Alice Bob Cindy Doug Ed Fred ]  k = 3
Cindy is out
Queue: [Doug Ed Fred Alice Bob ]  k = 3
Fred is out
Queue: [Alice Bob Doug Ed ]  k = 3
Doug is out
Queue: [Ed Alice Bob ]  k = 3
Bob is out
Queue: [Ed Alice ]  k = 3
Ed is out
First winner is Alice
Queue: [Gene Hope Irene Jack Kim Lance ]  k = 6
Lance is out
Queue: [Gene Hope Irene Jack Kim ]  k = 6
Gene is out
Queue: [Hope Irene Jack Kim ]  k = 6
Irene is out
Queue: [Jack Kim Hope ]  k = 6
Hope is out
Queue: [Jack Kim ]  k = 6
Kim is out
Second winner is Jack
intranet (56) %
Double-Ended Queue

A queue-like structure that supports insertion and deletion at both the front and the rear of the queue. Such an extension of a queue is called a double-ended queue, or deque, or "D.Q.".

The fundamental methods of the deque ADT are: addFirst, addLast, removeFirst, removeLast, getFirst, getLast, size, and isEmpty. What would be an ideal data structure to implement this deque ADT?

 

Priority Queue
A priority queue (P.Q.) behaves much like an ordinary queue:
  • Elements are placed in the queue and later taken out.
  • Each element in a priority queue has an associated number called its priority.
  • When elements leave a priority queue, the highest priority element always leaves first.

A P.Q. Implementation Using an Ordinary Queue

  • Define an array of ordinary queues, called queues[].
  • Items with priority 0 are stored in queues[0]. Items with priority 1 are stored in queues[1]. And so on, up to queues[highest_priority].
  • When an item with priority i needs to be added, we insert it to the end of queues[i].
  • When an item needs to be removed, we move down through the ordinary queues, starting with the highest priority, until we find a nonempty queue. We then remove the front item from this nonempty queue. For efficiency, we could keep a variable to remember the current highest priority.

Last updated: Jul. 2013