# Lecture 20 Handout

Priority Queue

## Learning Outcomes

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

• Describe what Priority Queue is and how it is different from standard Queue.
• Describe how “natural ordering” of objects is established in Java.
• Explain the difference between Comparable and Comparator interfaces.
• Describe the core operations of Priority Queue.
• Enumerate the structure and order properties of binary heap.
• Differentiate binary search trees from binary heaps.
• Explain and trace the core operations of Priority Queue with Binary Heap implementation.
• Understand the operations of Priority Queue with Binary Heap well enough to implement it.
• Contrast efficiency of alternative implementation approaches (e.g. sorted/unsorted sequence vs binary heap).
• Explain how a binary heap can be represented using a (ranked) array.

## Lecture Plan

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

Lessons marked with ⚡ contain exercise/activity.

A Priority Queue is a variation of a standard queue where instead of being a “first-in-first-out” data structure, values come out in order of highest priority.

The “priority” by default is based on the natural ordering of the elements. If priority is same for two elements then they are removed (dequeued) on the basis of first come first serve.

In this lecture, we will explore a very interesting data structure called binary heap that enables us to efficiently implement PriorityQueue ADT.

## Submission class ↗

Open Submission.java in the demo package of the starter code. The Submission class represents a (homework) submission made by a student. Among its attributes are:

private int positionInQueue;
private String student;
private int numPriorSubmission;

All submissions are positioned one after another in order they are received. Submissions are comparable to one another, based on the order they were received (their position in the submission queue).

@Override
public int compareTo(Submission other) {
return this.positionInQueue - other.positionInQueue;
}

We will use the Submission class to motivate the idea of a priority queue.

## Sorting submissions ↗

Suppose we have a collection of submissions. We want to sort the submissions according to their “natural ordering”.

Natural ordering, here, means the default ordering of objects of a specific type when they are sorted in an array or a collection.

For example, the natural ordering of String objects is alphabetic order. The natural ordering of Date objects is chronological order.

Java allows you to define the natural ordering of objects of a specific type by implementing Comparable interface.

A class that implements the Comparable interface is said to have natural ordering. And, the compareTo() method is called the natural comparison method.

So the natural ordering of Submission is based on the order they were received, as it is defined in the Submission.compareTo method.

If we instantiate several objects of type Submission and store them in a Java Collection such as a List, then we can use Collections.sort to order (sort) the submissions based on their natural ordering.

An example is provided in the Main.java in the demo package of the starter code.

List<String> students = getStudents();
List<Submission> submissions = getSubmissions(students);

System.out.print("Submission sorted (natural ordering):");
Collections.sort(submissions);
System.out.println(submissions);

## Prioritizing Submissions ↗

A submission that was made earlier will be graded earlier. This is an example of a first-in-first-out situation and a Queue will be a natural choice to store submissions.

Imagine we want to prioritize grading of submissions on AutoGrader. We want to consider the number of prior submission by a student, in addition, or instead of, the order which the submission was received. For the sake of this contrived example, assume the fewer prior submissions you’ve made the higher the priority of your submission to get AutoGrader processing time.

We can sort (order) the submissions, using Collections.sort, based on the number of prior submissions but we need to overwrite the natural ordering of Submission objects. In Java, you can do this by providing a Comparator to the Collections.sort. An example is provided in the Main.java in the demo package of the starter code.

List<String> students = getStudents();
List<Submission> submissions = getSubmissions(students);

System.out.print("Submission sorted (priority ordering):");
Collections.sort(submissions, new LessSubmissionHigherPriority());
System.out.println(submissions);

where LessSubmissionHigherPriority is defined as:

private static class LessSubmissionHigherPriority
implements Comparator<Submission> {

@Override
public int compare(Submission s1, Submission s2) {
return s1.getNumPriorSubmission() - s2.getNumPriorSubmission();
}
}

## Java's Comparator ↗

Java’s Comparator<T> is an interface that defines a compare(T o1, T o2) method with two arguments that represent compared objects.

The compare method works similar to the Comparable.compareTo() method:

• return $0$ when two objects o1 and o2 are equal.
• return a value $> 0$ when the first object is greater than the second one.
• return a value $< 0$ when the first object is less than the second one.

So to give higher order to a submission with less prior submissions, we must create the following Comparator:

private static class LessSubmissionHigherPriority
implements Comparator<Submission> {

@Override
public int compare(Submission s1, Submission s2) {
return s1.getNumPriorSubmission() - s2.getNumPriorSubmission();
}
}

We can (just to have fun) create a Comparator to reverse the natural ordering of submissions:

private static class ReverseNaturalOrdering
implements Comparator<Submission> {

@Override
public int compare(Submission s1, Submission s2) {
// notice s2 is compared to s1, the order matters!
return s2.compareTo(s1);
}
}

The natural ordering of submissions would be met by the following Comparator:

private static class NaturalOrdering
implements Comparator<Submission> {

@Override
public int compare(Submission s1, Submission s2) {
return s1.compareTo(s2);
}
}

I encourage you to use the “demo” (Main.java in the demo package of the starter code) to experiment with Comparators.

Resources

## PriorityQueue Interface ↗

Here is an interface for the PriorityQueue ADT.

/**
* Priority Queue of ordered values.
*
* @param <T> Element type.
*/
public interface PriorityQueue<T extends Comparable<T>> {
/**
* Insert a value.
*
* @param t Value to insert.
*/
void insert(T t);

/**
* Remove best value.
*
* @throws EmptyException If queue is empty.
*/
void remove() throws EmptyException;

/**
* Return best value.
*
* @return best value in the queue.
* @throws EmptyException If queue is empty.
*/
T best() throws EmptyException;

/**
* Check if no elements present.
*
* @return True if queue is empty, false otherwise.
*/
boolean empty();
}

Notice the elements of PriorityQueue must be Comparable.

By default, a PriorityQueue will order (prioritize) the elements based on their natural ordering.

The best can be defined to return the largest (maximum) or smallest (minimum) value. By contract, we define best to return the largest value.

Any implementation of PriorityQueue must provide two constructors: a default constructor (with no argument) and a non-default one which allows a Comparator to be provided to overwrite the natural ordering of the element types.

As a further clarifying point, recall a queue is not a set, so duplicate values are allowed.

## PriorityQueue: Implementation ↗

Suppose we want to implement the operations of PriorityQueue using a linked-list or an array-based implementation.

Exercise Provide an example of core operations, for each underlying data structure, where the operation cannot be implemented better that $O(n)$.

Hint: We have, generally, two choices: keeping the underlying data ordered (sorted) or not.

Solution

Keeping the underlying data sorted will enable us to perform remove and best in constant time but insert will be $O(n)$ since an array implementation will require shifting elements around and a linked-list implementation will require a linear search to find where to insert.

Keeping the underlying data unordered (unsorted) will require us to perform linear search for finding/removing the best (whether we use array or a linked list implementation).

## PriorityQueue: Tree Implementation ↗

We are going to explore a tree-based implementation of PriorityQueue. This implementation is called a Binary Heap (or simply a Heap).

A heap has the following properties:

• Structure (shape) property: heap is a complete binary tree.

The structure property implies the height of the tree is $O(\lg n)$.

• Order (heap) property: the element stored at each node has a higher priority (is considered “better”) than its children.

The order property implies the “best” element is always at root.

## Heap: Structure Property ↗

Recall a full binary tree is a tree in which every node other than the leaves has two children.

A complete binary tree is one where the tree is “full” on all levels except possibly the last, and the last level has all its nodes to the left side.

Exercise Which of the following are a complete binary tree?

Solution

The two on the top are complete binary trees but the bottom two are not.

## Heap: Order Property ↗

The ordering can be one of two types:

• The min-heap property: the value of each node is less than or equal to the value of its children, with the minimum-value element at the root. (Here “best” means “smallest”.)

• The max-heap property: the value of each node is greater than or equal to the value of its children, with the maximum-value element at the root. (Here “best” means “largest”.)

The order property of a heap is a weaker ordering requirement compared to that of a binary search tree. In a max-heap for instance, the value in each node is $\ge$ to the value stored in its children but not necessarily $\ge$ to all its descendants.

Exercise Which of the following are a valid binary (max) heap?

Solution

The two on the top are valid binary (max) heap but the bottom two are not.

## Heap: Ranked Array Representation ↗

A complete binary tree can be uniquely represented by storing its level order traversal in an array.

The root is the second item in the array. We skip the index zero cell of the array for the convenience of implementation. Consider $k^{\text(th)}$ element of the array,

• its left child is located at 2 * k index
• its right child is located at 2 * k + 1 index
• its parent is located at k/2 index

## Heap: Best ↗

As stated earlier, the “best” element is always at the root of a binary heap implementation of PriorityQueue ADT.

Therefore best() is $O(1)$ operation returning the value stored at root.

## Heap: Insert ↗

Consider we have the following min-heap:

To insert a new element, such as $7$, we initially appended to the end of the heap. (So it goes to the last level, after the last element from left to right.)

We may have to “repair” the heap property by percolating the new node up to its proper position. This process is often called swim up. It involves comparing the added element with its parent and moving the added element up a level (swapping with the parent) if needed.

The comparison is repeated until the parent is smaller than or equal to the percolating (swimming) element.

The worst-case runtime of the algorithm is $O(\lg n)$, since we need at most one swap on each level of a heap on the path from the inserted node to the root.

## Heap: Remove ↗

Consider we have the following min-heap:

To remove the best, that is to remove the minimum element, we can replace the root with the last element of the heap.

Then, we will delete the last element.

Finally, we restore the heap property by percolating the new root down. This process is often called sink down. It involves comparing the added element with its children and moving the added element down a level (swapping with the smaller of the two children) if needed.

The process is repeated until the children are smaller than or equal to the percolating (sinking) element.

Or, until the percolating (sinking) element reaches the deepest level.

Similar to insertion, the worst-case runtime for remove is $O(\lg n)$ since we need at most one swap on each level of a heap on the path from the root node to the deepest level.

## Heap Operations: Exercise ↗

Exercise Consider an empty min-heap. Show (draw) the resulting heap in tree form after each of the following operations:

insert(10)
insert(5)
insert(8)
insert(1)
insert(14)
remove()
insert(12)
insert(3)
insert(7)
remove()
Solution