In this reading, we will learn about the last new data structure of the quarter!

I think you have some priority issues

Consider if you were writing software for a hospital to manage the waiting list of patients. Your system should be able to add new patients in to the waiting list and remove a patient that should be served next; both of these operations should be as efficient as possible. Why not use a Queue for this situation? Doesn’t it efficiently implement add and remove (both in O(1) time)?

Well, this problem is a bit different than ones we’ve seen before because in real life, we don’t really care about who showed up at the hospital first, but rather who is in the most urgent need for help. If we were to use a plain old Queue, we would be serving patients in a first-in-first-out (FIFO) manner; this means someone who stubbed their toe would be seen before someone with a life-threatening wound just because they showed up first, which does not seem right.

Instead, we would want a data structure that can efficiently tell us who the next most-urgent patient is (i.e. the next patient with the highest priority). Let’s consider how to solve this problem with data structures we have seen.

  • Unsorted List: Maintain a list of patients in an unsorted fashion, and search the list for the highest priority element
    • add: Just append at end O(1)
    • remove: Search the list for the highest priority element O(n)
  • Sorted List: Keep the elements in a sorted list sorted by priority so you always know who comes first
    • add: Find the right place to insert into the list and the insert at that position O(n)
    • remove: Remove the element at the front of the list O(1)
  • Store the elements in a TreeSet sorted by priority
    • add: Add to the set O(log n)
    • remove: Remove from the set O(log n)
    • Is a bit tricky since sets only allow unique values so we wouldn’t be able to have multiple elements with the same value. Seems a bit hacky to use this approach.

It seems that none of the data structures we have seen so far can handle this situation really well. The TreeSet is almost there, but doesn’t seem like this is a scenario where a Set applies since we might not have unique values in every context.

Since problems like managing a list of patients comes up a lot, there is a new data structure we will learn about called a PriorityQueue that will handle these situations with priority for us.

PriorityQueue

A PriorityQueue is a type of Queue that removes elements according to their priority rather than in a FIFO manner. The PriorityQueue is special because it efficiently adds elements in and provides fast access to the highest priority element in the structure.

In Java, we have a class called PriorityQueue that implements the Queue interface that has all the Queue methods you are familiar with (add, remove, peek, size, isEmpty). The only difference is that the PriorityQueue will remove the elements in sorted order (smallest value first). For example if we had the following code segment

Queue<String> tas = new PriorityQueue<>();
tas.add("Lisi");
tas.add("Connor");
System.out.println(tas.remove());  // Connor

The great thing about coding to the Queue interface is now you know exactly how to use PriorityQueues! They are exactly like regular Queues with the slight difference of the order returned.

Common Gotchas with PriorityQueue

Let’s compare

Suppose I was running into a budget crisis and needed to lay off some TAs. To save money, I want stop employing the oldest TAs since they are getting paid the most.

First we will define a class called TA to keep track of information about our TAs

public class TA {
    private String name;
    private int quarters;

    public TA(String name, int quarter) {
        this.name = name;
        this.quarters = quarters;
    }

    public String getName() {
        return name;
    }

    public int getQuarters() {
        return quarters;
    }

    public String toString() {
        return name + ": " + quarters;
    }
}

To figure out who’s the oldest, I’ll put the TAs into a PriorityQueue and then use that to figure out who the oldest is. What does the following code snippet print?

Queue<TA> tas = new PriorityQueue<>();
tas.add(new TA("Andy", 7));
tas.add(new TA("May", 3));
tas.add(new TA("Ketaki", 15));
System.out.println(tas);

We actually end up getting the following exception!

Exception in thread "main" java.lang.ClassCastException: TA cannot be cast to java.base/java.lang.Comparable
    at java.base/java.util.PriorityQueue.siftUpComparable(PriorityQueue.java:653)
    at java.base/java.util.PriorityQueue.siftUp(PriorityQueue.java:648)
    at java.base/java.util.PriorityQueue.offer(PriorityQueue.java:342)
    at java.base/java.util.PriorityQueue.add(PriorityQueue.java:323)
    at Test.main(Test.java:2)

Why did this happen? Well, PriorityQueue needs to know how to order the elements it contains so it can order them by priority, but Java doesn’t know how our TAs should be sorted.

How do we fix this? We have to tell Java how to sort our class by using the Comparable interface! As a review, to implement Comparable so that the most senior TAs appear first we would change the TA class to:

public class TA implements Comparable<TA> {
    private String name;
    private int quarters;

    public TA(String name, int quarter) {
        this.name = name;
        this.quarters = quarters;
    }

    public String getName() {
        return name;
    }

    public int getQuarters() {
        return quarters;
    }

    public String toString() {
        return name + ": " + quarters;
    }

    public int compareTo(TA other) {
        return other.quarters - this.quarters;
    }
}

Let’s look at the output

Okay, so now our code snippet from above will work and should print the TAs out in sorted order! If we run it again we get the output

[Ketaki: 15, May: 3, Andy: 7]

Huh, that doesn’t look right. What’s going on?

It turns out that when you print a PriorityQueue, it does not print in sorted order! This is because the PriorityQueue promises that the elements will be removed in sorted order, but does not promise anything else about how it actually stores it.

If you take a further CSE class like CSE 332 or CSE 373, you will learn how to implement the data structure behind PriorityQueue (it’s called a heap), but for 143, you only have to know that you have to actually remove things from the PriorityQueue to get the elements in sorted order.

So as a reminder to help you if you have a bug in a program that involves PriorityQueues

  • Make sure the elements of the PriorityQueue are Comparable
  • To debug programs with PriorityQueues, you aren’t able to just print the queue out but rather you have to write some debug code to loop through the queue and print out values as your remove them to see what the sorted order is.