Per usual, with a new data structure, let’s think about some motivations for new problems that our current tools don’t adequately address…
Motivation
How about a nice motivating example to get us started?
Example
You are managing a massive, worldwide delivery service, and need to assess some Priorities of deliveries so that the highest Priority items are delivered before the others.
Let’s think about some challenges in this scenario:
Question
What might be some challenges associated with maintaining this collection of orders as a List or a BinarySearchTree?
Answer
Some issues, among others:
- Since priorities might not be unique, a BST can be challenging to use, and even if they were unique, searching for the highest priority would either mean having an unbalanced tree or one that still takes O(log(n)) to find.
- Searching for the highest-priority order would be an O(n) operation on an unsorted list, or O(log(n)) on a sorted one.
- Maintaining an ArrayList means that as orders with different priorities come in, much shifting might be necessary to insert the new orders.
- Maintaining a LinkdList is immediately infeasible since its search would be O(n) at best, unable to take advantage of binary search on an ArrayList.
Remark
As such, it seems that all of our existing techniques lack some desire in this example, and we should make it a priority to find some solutions to the following:
Our objective(s) will be to find a data structure that:
- Returns the highest “need / priority” item in some collection as quickly as possible.
- Can efficiently handle insertions / removals of items while still guaranteeing this first property.
- Can even be used to find the n-greatest need items from a larger collection.
Basics
Definition
A Priority Queue is a generalized version of a queue in which items can be retrieved according to the ranking / ordering of some arbitrary criteria.
This might mean, e.g.:
- Provide the next patient with the “highest-need” triage score.
- Provide the next closest missile with the “highest-risk” threat score.
- Attempt the next best move for an artificial agent solving a problem.
Priority Queues are useful for pretty much anything to which you can assign some rankable-score, and in which it’s important that we receive the next “highest-priority” immediately while being able to store them effectively, and retrieve the next highest priority quickly as well.
Question
By what definition of “priority” would a standard FIFO Queue be considered a special case of a Priority Queue?
Answer
Order of insertion! Standard Queue items are simply removed in the FIFO order of their insertion; we could mimic this behavior with a priority queue by simply logging the order of insertion and always removing the one with the smallest order counter first.
Heaps
To implement a Priority Queue, let’s talk about a new tree-based data structure. The heap!
Definition
A heap (generally referring to a max heap) is a special type of tree where all subtrees of a given Node have values less than or equal to that Node’s.
Debug
WARNING: The heap data structure is on no way related to the heap where objects are stored!
Such unfortunate naming schemas in computer science… it’s not like humans have infinite generative language capacities or anything…
Toolkit
Heaps are typically used when we need immediate access (O(1)) to the largest / smallest “priority” element in a collection, and can quickly reorder the collection to maintain this property at all times.
Here’s a fun comic that illustrates how they work:
Question
Examining this amusing XKCD, we do notice something interesting about the heap of presents: where is the largest present located, and why is this interesting / useful?
Answer
The largest is located at the root! This means we could access it in constant time, since we always know where the root of a tree is.
In the above comic, we see that the heap of presents at the base has the largest at the top with children decreasing in size with greater depth.
Our discussion, however, will be restricted to binary heaps, which have some additional restrictions.
Definition
A binary heap is a complete binary tree where all subtrees of a given Node have values less than or equal to that Node’s.
So we’re restricting the number of children any Node can have to 2, AND requiring the binary tree be complete.
Definition
A complete binary tree is a binary tree with all full levels, except for possibly the last level, in which case it is required that the Nodes be filled from left to right without any spaces.
Example
Is the following binary tree complete? Is it a heap?

Example
Is the following binary tree complete? Is it a heap?

Example
Is the following binary tree complete? Is it a heap?

Example
Is the following binary tree complete? Is it a heap?

Question
Is a single-node tree a heap?
Answer
Yes! It must be complete because the last (and only) level is full, and its value must be larger than its subtrees because it has no subtrees!
Question
How many unique structural configurations (ignoring node values) are there for a complete binary tree with N nodes?
Answer
Just 1! If you were to change the position of any node in the structure, it would cease to be complete.
Heap Properties
Heaps have some nice properties, for example:
Definition
The largest value of a (max)heap is always at the root, and therefore accessible with constant time, O(1)!
Definition
Because heaps are complete trees, we can represent them as arrays to enable random access, with indexing starting at the root and numbering nodes level-by-level from left-to-right.
Question
What performance benefit does representing a complete tree as an array have over a Node-with-pointers implementation?
Answer
Arrays are indexed, meaning we can access any element with constant time, O(1).

“Nice colors in that graph, Andrew, do you also design children’s play equipment?”
Shhh! It was illustrative!
Definition
We know that in the array representation of a heap, the root must always be at index 0.
But how do we determine the children and parent of any given index in an array?
Well, since it’s a complete tree, we have a simple computational way of finding these indexes…
Question
Given the index n of a “Node” in our conceptual tree structure, how can I get the index of the parent?
Answer
Index of parent: (n - 1) / 2; (NOTE: this is integer division, so we ignore the decimal of the quotient)
So, we can define a simple functional mapping, pretending that we have a Heap class (to be done later):
private int getParent (int index) {
return (index - 1) / 2;
}Getting the left and right children is a similar mechanical exercise:
// Child is either 'L' or 'R'
private int getChild (int index, char child) {
int result = (index * 2) + 1;
if (child == 'R') {
result++;
}
return result;
}Example
Draw the binary tree representation of the following heap:

Now that we have the basic tools down, we’re ready to go over some heap algorithms!
Heap Operations
The key with any heap operation is that it leaves the resulting data in heap format.
So, whenever we modify the contents of a heap, we need to make sure that we “reheapify” its elements afterwards.
This maintains the constant time access of the largest element at the root.
Definition
After every operation on a heap, we have some general steps: perform the operation and then “reheapify” the binary tree.
Insertion
For insertion, that looks like this:
- Create a complete binary tree with the newly inserted node in its proper location as the left-most vacant leaf position on the last level of the tree (i.e., the only place we could insert a leaf and still have the tree be complete).
- Starting at that newly inserted node, bubble upwards ensuring that each node is less than its parent.
- As soon as a parent is greater than the node we’re bubbling, stop—your array is now reheapified! (assuming it was a heap before the insertion).
Question
Why is it sufficient to conclude that in step 3, when the parent is greater than the node we’re bubbling, that we may stop? Why must we not also continue checking farther up the heap to verify its heapiness?
Answer
By assumption, we are inserting into a structure that is already a heap. This means that all nodes are already abiding by the definition of a heap. So, when we insert a new node, and stop bubbling up whenever the parent is larger than what we’re bubbling, we can trust that all nodes updwards are already heap-compliant.
Example
Using the above steps, insert the node with value 50 into the heap below:




Got that?
Let’s make a simple BinaryMaxHeap class with this algorithm!
I’ll start you off…
Example
Complete the insert function and its helper, bubbleUp, in our BinaryMaxHeap below:
package tree.heap;
import java.util.*;
/**
* Binary Max Heap storing Integer values representing
* priorities of any arbitrary complex Node we could store
*/
public class BinaryMaxHeap {
// Fields
// -------------------------------------------------------------------------
private ArrayList<Integer> heap;
// Constructor
// -------------------------------------------------------------------------
public BinaryMaxHeap () {
this.heap = new ArrayList<>();
}
// Methods
// -------------------------------------------------------------------------
/**
* Adds the given int (representing a priority) to the
* proper spot in the heap
* @param toAdd Priority we desire to add
*/
public void add (Integer toAdd) {
// Step 1: Maintain a complete binary tree by adding the
// new value at the bottom-most, right-most leaf spot, or
// an append operation into the array
this.heap.add(toAdd); // Appends toAdd to end
// Step 2: Reheapify: bubble-up the value from its current
// index into one where it is <= its parent's
this.bubbleUp(this.heap.size()-1);
}
// Helper Methods
// -------------------------------------------------------------------------
/**
* Recursively bubbles up the value at the given index until
* its priority is less than or equal to its parent's
* @param index Index at which to check for bubbling
*/
private void bubbleUp (int index) {
// Base case:
if (index == 0) { return; } // At root, no where else to bubble!
int parentIndex = getParentIndex(index),
parentPri = heap.get(parentIndex),
currentPri = heap.get(index);
// Check if parent's priority is less than the current, if so
// bubble up!
if (parentPri < currentPri) {
// bubble-up == swap operation with parent
// Visit behavior:
heap.set(index, parentPri);
heap.set(parentIndex, currentPri);
// Recursive case:
bubbleUp(parentIndex);
}
}
/**
* Return the index of the parent of the given child in the
* array representing this heap
* @param childIndex Index of child from which to get the parent's
* @return Parent's index of the given child
*/
private int getParentIndex (int childIndex) {
return (childIndex - 1) / 2; // [!] Integer division
}
/**
* Returns the index of the given 'L' or 'R' child given the parent
* @param parentIndex Index of the parent in the array
* @param child 'L' or 'R' for desired child
* @return Index corresponding to the parent's 'L' or 'R' child
*/
private int getChildIndex (int parentIndex, char child) {
int result = (parentIndex * 2) + 1; // Index of Left child
if (child == 'R') { result++; }
return result;
}
@Override
public String toString () {
return this.heap.toString();
}
// Empirical Tests
// -------------------------------------------------------------------------
public static void main(String[] args) {
BinaryMaxHeap b = new BinaryMaxHeap();
b.add(25);
b.add(10);
b.add(20);
b.add(8);
System.out.println("BEFORE adding 50:");
System.out.println(b);
b.add(50);
System.out.println("AFTER adding 50:");
System.out.println(b);
}
}Great! On to deletion.
Removal
Nothing particularly surprising about deletion; it’s essentially the same algorithm as insertion, in reverse!
Toolkit
Just like we might pop an item off the top of a Stack, we also typically remove the root of a heap to get the next largest / smallest elemetn.
That said, the algorithm for removing items from a heap is generalized to removing an item at any index (thus the beauty of recursive data structures).
- Remove the target node from the heap
- Promote the bottom-right-most leaf of the last level to the position of the removed node
- Trickle down the promoted item such that if either of its two children are greater than it, then it trades places with the greatest of its children.
- Continue trickling down until no child is greater than the trickling node.
Let’s try removing the 50 node at the root from the heap below:




Example
I shall leave it as an exercise to implement the deletion function into our BinaryMaxHeap class!
Question
What is the time complexity of insertion? Of deletion?
Answer
Both are O(log(n)), for the same reason that binary search was. At each step of bubbling, we’re ignoring 1/2 of the other nodes in the heap.
Question
To find an element in a heap, can we perform the same binary search algorithm on heaps that we can on binary search trees?
Answer
No, we do not have the same guarantee that an item must be found in one subtree or another; it could be at either if it is less than a given Node N.
Priority Queues
Definition
Priority Queues are a data type that are implemented using the heap data structure to manage collections whose contents specify a definition of priority.
Toolkit
The
PriorityQueueclass is the JCF implementation which is implemented using a min-heap. It has essentially the same interface as a regular FIFO Queue (addto add items andpollto remove the highest priority), but is instead ordered by each item’s priority.
Example
Here’s a small example of a Priority Queue of Integer values; for Integers, their priority is the same as their value.
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.add(20);
pq.add(10);
pq.add(30);
// [?] What gets printed below?
System.out.println(pq.poll());
System.out.println(pq.poll());
System.out.println(pq.poll());Comparable Interface
Toolkit
Note that the Java
PriorityQueueis another Java Generic that can store any reference-type.
Question
Suppose we wished to store Forneymon in a PriorityQueue. What would we have to specify?
Answer
How we associate some value of Priority with a Forneymon!
Toolkit
There are couple of different ways that you can specify how objects of custom types are prioritized:
- Have the class implement the
Comparableinterface, and define apublic int compareTo(T other)method.- Construct a PriorityQueue with a
Comparatorobject.
Remark
The first method is appropriate when all instances of a class will be prioritized the same way, the second when you might prioritize them differently under different circumstances.
Let’s take a look at using the Comparable interface in this lecture.
Example
Consider a
ForneymonTriageclass that’s meant to prioritize treating the most wounded Forneymon (i.e., lowest health) before others. Implement theComparableinterface in the Forneymon class to enable this.
Toolkit
Step 1: have the class implement
Comparableand stub the methodpublic int compareTo(T other)whereTspecifies what classes the implementing one should be comparable to.
// [!] Note: Comparable is a generic as well, so we must specify what Forneymon are
// comparable to -- in this case, we want it to be comparable to other Forneymon
abstract public class Forneymon implements Comparable<Forneymon> {
// ... Other Forneymon contents elided
public int compareTo (Forneymon other) {
throw new UnsupportedOperationException();
}
// ... Other Forneymon contents elided
}Toolkit
Step 2: implement the
compareTomethod, which returns an integer that is:
0ifthisForneymon and theotherForneymon have equal priority.< 0ifthisForneymon has higher priority than theother(will be nearer to the top of the minheap).> 0ifthisForneymon has lower priority than theother(will be nearer to the bottom of the minheap).
public int compareTo (Forneymon other) {
// [!] Will be < 0 (higher priority) when this Forneymon has less health
// than the other!
return this.health - other.health;
}Finally, testing it, we see that our most wounded Forneymon will be polled first!
PriorityQueue<Forneymon> fmTriage = new PriorityQueue<>();
Burnymon b1 = new Burnymon("b1"),
b2 = new Burnymon("b2"),
b3 = new Burnymon("b3");
b2.takeDamage(10, "Burny");
b3.takeDamage(5, "Burny");
fmTriage.add(b1);
fmTriage.add(b2);
fmTriage.add(b3);
// [?] In what order will the Burnymon be polled below?
while (!fmTriage.isEmpty()) {
System.out.println(fmTriage.poll());
}Remark
That’s it! Internal to the
PriorityQueueimplementation, its contentsthis.compareTo(parent)method is called during the bubble-up process andthis.compareTo(child)during bubble-down to decide if a swap is necessary or not!
Hidden on the original page
The section below was commented out of the Fall 2021 course notes, so students never saw it. It is preserved here because the material is complete and usable.
HeapSort
By observing the operations above, we can start to glean some insight on a potential application:
- Getting the largest item in a MaxHeap can be done in O(1).
- Removing an item from the heap gives us constant time access to the next largest item after the one just removed.
- Reheapifying after a removal takes only O(log(n)) time.
Question
How might we be able to make use of the above two facts?
Answer
Sorting! Successively pop the largest item from a heap and it can be easy to obtain a sorted list!
Definition
HeapSort is a reduce-and-conquer algorithm for obtaining a sorted list of items by successive removal of the greatest item in an iteratively shrinking MaxHeap.
Its algorithm goes something like this:
count = size of heap
while count > 1
swap the root with the deepest, right-most leaf
(i.e., swap index 0 with index (count-1))
count--
re-heapify everything to left of countToolkit
Note how this is a decrease-and-conquer algorithm, since at the end of each iteration (similar to InsertionSort), the next-largest item is locked in its proper place at the end of its sublist.
Since we are representing heaps in their array format, what we are left with after heapsort is a sorted array of that heap’s elements.
Let’s do some heap sorting!






Question
What is the time complexity of heapsort?
Answer
For each of n items: getting the largest takes O(1), and popping + reheapifying takes O(log(n)). As such, HeapSort exhibits a best, worst, and average case performance of O(n * log(n)).
How exciting! That looks a lot better, on average, than InsertionSort, even though Insertion can win in the best case.
We see that these two sorting algorithms are similar insofar as they maintain a sorted sub-list that grows incrementally until we are left with a sorted version of the original list.
Here’s a cool animation of that shamelessly stolen from Wikipedia:

Image source from good ole Wikipedia: HeapSort Stuff
