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 elementadd
: Just append at endO(1)
remove
: Search the list for the highest priority elementO(n)
- Sorted
List
: Keep the elements in a sorted list sorted by priority so you always know who comes firstadd
: Find the right place to insert into the list and the insert at that positionO(n)
remove
: Remove the element at the front of the listO(1
)
- Store the elements in a
TreeSet
sorted by priorityadd
: Add to the setO(log n)
remove
: Remove from the setO(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 PriorityQueue
s! They are exactly like regular Queue
s 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 TA
s 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 PriorityQueue
s
- Make sure the elements of the
PriorityQueue
areComparable
- To debug programs with
PriorityQueue
s, 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.