Lecture 13 Handout

Queue

Learning Outcomes

At the end of this lecture, you’ll be able to:

Lecture Plan

In this lecture, we'll cover the following lessons:

  1. Queue Abstract Data Type
  2. Queue Interface
  3. Linked Implementation of Queue
  4. Trace Linked Implementation
  5. LinkedQueue
  6. Array Implementation of Queue
  7. Trace Array Implementation
  8. ArrayQueue
  9. Restricted Data Structures: Mixing it up!

Lessons marked with ⚡ contain exercise/activity.

Downloads

Queue Abstract Data Type

A queue ADT supports two main operations:

The order in which elements are removed gives rise to the term FIFO (first in, first out) to describe a queue.

It helps to visualize a queue as an ADT where elements are removed from the front but added to the back, analogously to when people line up to wait for goods or services.

A queue has a variety of applications such as:

Resources

Queue Interface

Here is the Queue interface which we use in this course:

/**
 * Queue ADT.
 *
 * @param <T> base type.
 */
public interface Queue<T> {

  /**
   * Adds a new element to the back of this queue.
   *
   * @param value to be added
   */
  void enqueue(T value);

  /**
   * Removes the element at the front of this queue.
   *
   * @throws EmptyException when empty() == true.
   */
  void dequeue() throws EmptyException;

  /**
   * Peeks at the front value without removing it.
   *
   * @return the value at the front of this queue.
   * @throws EmptyException when empty() == true.
   */
  T front() throws EmptyException;

  /**
   * Checks if empty.
   *
   * @return true if this queue is empty and false otherwise.
   */
  boolean empty();
}

Make note of enqueue and dequeue: one adds the other removes. Also, there is a front which allows you to peek at the front of the queue without removing the front element.

Linked Implementation of Queue

A singly linked list can be used to implement the Queue ADT efficiently as long as it has a tail pointer (as well as the head pointer). The tail pointer is a reference variable pointing to the other end (opposite of head) of the queue.

Exercise Think about how to implement a queue using a singly linked list such that the core operations are constant time.

OperationHow?Time
enqueue$O(1)$
dequeue$O(1)$
front$O(1)$
empty$O(1)$
Solution

The front of the queue would be the HEAD node of the singly linked list. The back of the queue would be the TAIL node of the singly linked list. Every time we want to enqueue an additional value to the queue, we would create a new node and set it as the new TAIL node. Then when we want to dequeue a value from the front of the queue, we set the HEAD to the current HEAD node’s .next. As such, the HEAD node will always be at the front of the queue, and calling front would return the value of the HEAD node.

OperationHow?Time
enqueueprepend the list and update the tail$O(1)$
dequeuedelete from front: head = head.next$O(1)$
frontreturn head.data$O(1)$
emptycheck if head is null$O(1)$

Trace Linked Implementation

Think about a linked implementation of queue such that the core operations are constant time.

Exercise Complete the table below: update the linked list as you trace the operations; show value returned if any.

OperationLinked ListValue Returned
enqueue(1)HEAD -> (1) <- TAIL
enqueue(2)HEAD -> (1) -> (2) <- TAIL
enqueue(3)
dequeue()
enqueue(4)
dequeue()
enqueue(5)
front()
dequeue()
front()
Solution
OperationLinked ListValue Returned
enqueue(1)HEAD -> (1) <- TAIL
enqueue(2)HEAD -> (1) -> (2) <- TAIL
enqueue(3)HEAD -> (1) -> (2) -> (3) <- TAIL
dequeue()HEAD -> (2) -> (3) <- TAIL
enqueue(4)HEAD -> (2) -> (3) -> (4) <- TAIL
dequeue()HEAD -> (3) -> (4) <- TAIL
enqueue(5)HEAD -> (3) -> (4) -> (5) <- TAIL
front()HEAD -> (3) -> (4) -> (5) <- TAIL3
dequeue()HEAD -> (4) -> (5) <- TAIL
front()HEAD -> (4) -> (5) <- TAIL4
Resources

LinkedQueue

Exercise Open the starter code and complete the implementation of LinkedQueue. (Do this at home!)

Solution

Please check the posted solution.

Array Implementation of Queue

We want to implement the Queue interface using an array as an internal data storage.

Exercise Think about how to implement a queue using an array such that the core operations are constant time. Assume the array is sufficiently large.

OperationHow?Time
enqueue$O(1)$
dequeue$O(1)$
front$O(1)$
empty$O(1)$
Solution

Initialize two variables front and back to zero. When you equeue, add to the back. Use the back variable to index into the array. Then increment it. When you are asked for front element, simply return arr[front]. When you dequeue, simply increment front.

Since you remove from the “front” of the array, then there will be empty positions at the front. So, when the back variable reached the end of the array, it can wrap around it and write to the (actual) “front” of the array, to positions that were removed earlier.

This gives rise to a logical view of array being a circular data structure.

You can also dynamically grow the array when back reaches front.

OperationHow?Time
enqueuedata[back] = value and back = ++back % length$O(1)$
dequeuefront = ++front % length$O(1)$
frontreturn arr[front]$O(1)$
emptycheck if numElement == 0$O(1)$

The % length is a trick we use to reset the index when it reaches the length of the array. We could rewrite it as

font = front + 1;
if (front == length) {
  front = 0;
}

The example above replaces front = ++front % length. The same idea can be applied to updating back variable.

Resources

Trace Array Implementation

Think about an array based implementation of queue such that the core operations are constant time.

Exercise Complete the table below: update the array values at indices 0, 1, 2, 3, and 4 as you trace the operations; show value returned if any.

Operation[ 0 ][ 1 ][ 2 ][ 3 ][ 4 ]Return Value
enqueue(1)
enqueue(2)
enqueue(3)
dequeue()
enqueue(4)
dequeue()
enqueue(5)
front()
dequeue()
front()
Solution
Operation[ 0 ][ 1 ][ 2 ][ 3 ][ 4 ]Return Value
enqueue(1)1
enqueue(2)12
enqueue(3)123
dequeue()23
enqueue(4)234
dequeue()34
enqueue(5)345
front()3453
dequeue()45
front()454
Resources

ArrayQueue

Exercise Open the starter code and complete the implementation of ArrayQueue.

Notice the constructor of ArrayQueue does not take in a parameter for the size of the array. Feel free to initialize an array with an arbitrarily chosen capacity.

Solution

Please check the posted solution.

Restricted Data Structures: Mixing it up!

We can mix and match the operations of Stack and Queue to build up new data structures! Here are a few that you may come by in various references.

Steque ADT

A stack-ended queue or steque is a data structure that supports push, pop, and enqueue. Here is an example interface:

// A stack-ended queue.
public interface Steque<T> {
  // is empty?
  boolean empty();

  // adds a new element to top of Steque.
  void push(T value);

  // removes and returns top element.
  T pop() throws EmptyException;

  // adds a new element to bottom of Steque.
  void enqueue(T value);
}

So, you add from both ends but remove from one end. To better understand this ADT, consider the following sequence of operations and the schematic representation of steque after each operation (top of the steque is to the left):

steque.push(1);     // [1]
steque.push(2);     // [2, 1]
steque.enqueue(3);  // [2, 1, 3]
steque.enqueue(4);  // [2, 1, 3, 4]
steque.pop();       // [1, 3, 4]
Quack ADT

A queue-ended stack or quack is a data structure that supports push, pop, and dequeue. Here is an example interface:

// A queue-ended stack.
public interface Quack<T> {
  // is empty?
  boolean empty();

  // adds a new element to the top of Quack.
  void push(T value);

  // removes and returns top element.
  T pop() throws EmptyException;

  // removes and returns bottom element.
  T dequeue() throws EmptyException;;
}

So, you add from one end but remove from both ends. To better understand this ADT, consider the following sequence of operations and the schematic representation of quack after each operation (top of the quack is to the left):

quack.push(1);   // [1]
quack.push(2);   // [2, 1]
quack.push(3);   // [3, 2, 1]
quack.push(4);   // [4, 3, 2, 1]
quack.pop();     // [3, 2, 1]
quack.dequeue(); // [3, 2]
Deque ADT

Double Ended Queue (Deque) is a limited access data structure that allow insertion/removal at either end in $O(1)$.

  • Deque is a stack if you always add/remove at one end.
  • Deque is a queue if you always add to one end and remove from other.

Here is an example interface:

// A double-ended queue.
public interface Deque<T> {
    boolean empty();
    T front() throws EmptyException;
    T back() throws EmptyException;
    void insertFront(T t);
    void insertBack(T t);
    void removeFront() throws EmptyException;
    void removeBack() throws EmptyException;
}

The data structures above are not as widely uses as Stack and Queue (especially the first two). We leave it to you as an (unsolved) exercise to implement their operations.