Heap Applications

CSCI 1933 – Introduction to Algorithms and Data Structures
Adriana Picoral

Heap Applications

A heap is the best choice of data structure when we need quick access to the largest or smallest value in a queue. Some applications are:

  • Priority queues – Task scheduler, ER triage system
  • Heap Sort (optimized selection sort) – pretty good runtime, in-place, no quadratic worst-case
  • Graph algorithms, like shortest-path

ER triage system

  • Patients arrive with severity levels (1–5)
  • Use a max-heap (priority queue) to always treat the most critical patient next
  • Add complexity by having patients “timeout” if waiting too long

Discuss in small groups:

  • what classes do we need for this?
  • what data structure are we using?

RunER

ER is empty
4 patients waiting.
Next patient to be seen: Patient severity: 5, arrival time: 09:41:30.597749

Seeing patients: 
Patient severity: 5, arrival time: 09:41:30.597749
Patient severity: 4, arrival time: 09:41:30.588172
Patient severity: 2, arrival time: 09:41:30.597763
Patient severity: 1, arrival time: 09:41:30.597723
public class RunER {
    public static void main(String[] args) {
        Triage er = new Triage();
        System.out.println(er);

        er.insert(new Patient(4));
        er.insert(new Patient(1));
        er.insert(new Patient(5));
        er.insert(new Patient(2));

        System.out.println(er);

        System.out.println("Seeing patients: ");
        while (!er.isEmpty()) {
            System.out.println(er.seePatient());
        }
    }
}

Possible Solution

import java.time.LocalTime;
import java.time.Duration;

public class Patient implements Comparable<Patient> {
    private int severity;
    private LocalTime arrivalTime;

    public Patient(int severity) {
        if (!validSeverity(severity)) throw new RuntimeException("severity must be 1-5");
        this.severity = severity;
        arrivalTime = LocalTime.now();
    }

    private boolean validSeverity(int severity) {
        return severity >= 1 && severity <= 5;
    }

    public int getSeverity() { return severity; }
    public boolean setSeverity(int severity) {
        if (!validSeverity(severity)) return false;
        this.severity = severity;
        return true;
    }

    public LocalTime getArrivalTime() { return arrivalTime; }
    public double getWaitTimeMinutes() {
        Duration wait = Duration.between(arrivalTime,LocalTime.now());
        return wait.toMinutes();
    }

    public int compareTo(Patient other) {
        if (other == null) return 1;
        if (this == other) return 0;
        return this.severity - other.severity;
    }

    public String toString() {
        String out = "Patient severity: " + severity;
        out += ", arrival time: " + arrivalTime;
        return out;
    }
}

Possible Solution

public class Triage {
    private Patient[] patients;
    private int patientCount = 0;
    private int erCapacity = 100;

    public Triage() {
        patients = new Patient[erCapacity];
    }

    public int getPatientCount() {
        return patientCount;
    }

    private int parent(int i)    { return (i - 1) / 2; }
    private int leftChild(int i) { return 2 * i + 1; }
    private int rightChild(int i){ return 2 * i + 2; }

    private boolean hasParent(int i)     { return i > 0; }
    private boolean hasLeftChild(int i)  { return leftChild(i) < patientCount; }
    private boolean hasRightChild(int i) { return rightChild(i) < patientCount; }

    public boolean isEmpty() { return patientCount == 0; }


    // Returns the minimum element (root) without removing it — O(1)
    public Patient nextPatientInfo() {
        if (isEmpty()) throw new IllegalStateException("ER is empty");
        return patients[0];
    }

    // Inserts a new value and restores the heap property — O(log n)
    public void insert(Patient value) {
        if (patientCount == erCapacity) throw new IllegalStateException("ER is full");
        patients[patientCount] = value;   // place at the next open spot
        patientCount++;
        bubbleUp(patientCount - 1);   // restore heap property upward
    }

    // Removes and returns the minimum element — O(log n)
    public Patient seePatient() {
        if (isEmpty()) throw new IllegalStateException("ER is empty");
        Patient min = patients[0];
        patients[0] = patients[patientCount - 1];
        patientCount--;
        bubbleDown(0);
        return min;
    }

    private void swap(int a, int b) {
        Patient temp = patients[a];
        patients[a] = patients[b];
        patients[b] = temp;
    }

    private void bubbleUp(int i) {
        while (hasParent(i) && patients[i].compareTo(patients[parent(i)]) > 0) {
            swap(i, parent(i));
            i = parent(i);
        }
    }

    // used after pop min
    private void bubbleDown(int i) {
        while (hasLeftChild(i)) {
            int largerChild = leftChild(i);

            // find the smaller of the two children
            if (hasRightChild(i) && patients[rightChild(i)].compareTo(patients[leftChild(i)]) > 0) {
                largerChild = rightChild(i);
            }

            if (patients[i].compareTo(patients[largerChild])  >= 0) return;

            swap(i, largerChild);
            i = largerChild;
        }
    }


    public String toString() {
        if (isEmpty()) return "ER is empty";
        String out = "Patient list:\n";
        for (int i = 0; i < patientCount; i++) out += patients[i] + "\n";
        out += "\nNext patient to be seen: " + nextPatientInfo().toString();
        return out.trim();
    }

}

Heap Sort

Heap Sort

  • Optimized version of selection sort
  • Comparison-based sorting algorithm
  • Uses a binary Heap data structure – \(O(\log n)\)
  • Finds the maximum (or minimum) element and swaps it with the last (or first) element
  • Repeats for all elements until the array is sorted

Heap Sort achieves a time complexity of \(O(n \log n)\)

Heap Sort

Algorithm:

  1. The Array to be sorted is treated as a Complete Binary Tree
  2. Heapify the array – compare each parent with its children, ensuring the array is a max heap
  3. Instead of popping the max, we will move the root (max) to the last position in the array and make our max-heap one element shorter (we will do in-place sorting)

Heap Sort – Algorithm

  1. The Array to be sorted is treated as a Complete Binary Tree
  2. Heapify the array – compare each parent with its children, ensuring the array is a max heap
  • Build max heap – start from the last non-leaf node (n/2 - 1)
  • Go backwards (why?) – note that i is used as argument in bubbleDown

           for (int i = n / 2 - 1; i >= 0; i--) bubbleDown(arr, n, i);
           

Note bubbleDown takes an array as argument because we are writing static methods.

Heap Sort – Algorithm

  1. Instead of popping the max, we will move the root (max) to the last position in the array and make our max-heap one element shorter (we will do in-place sorting)
  • Extract max from heap one by one
  • Go backwards (why?)
           for (int i = n - 1; i > 0; i--) {
                swap(arr, 0, i);
                bubbleDown(arr, i, 0);
           }

Note swap and bubbleDown take an array as argument because we are writing static methods.

Heap Sort – Gradescope Submission

Test your code:

    public static void main(String[] args) {
        int[] arr = {12, 11, 13, 5, 6, 7, 0, 30, 2, 67};
        sort(arr);
        System.out.println(java.util.Arrays.toString(arr));
        // [0, 2, 5, 6, 7, 11, 12, 13, 30, 67]
    }

Submit your HeapSort.java to gradescope.

Solution

public class HeapSort {

    private static void swap(int[] arr, int indexOne, int indexTwo) {
        int temp = arr[indexOne];
        arr[indexOne] = arr[indexTwo];
        arr[indexTwo] = temp;
    }

    public static void sort(int[] arr) {
        int n = arr.length;

        // build max heap -- start from the last non-leaf node (n/2 - 1)
        for (int i = n / 2 - 1; i >= 0; i--)
            bubbleDown(arr, n, i);

        // extract elements from heap one by one
        for (int i = n - 1; i > 0; i--) { // backwards to ignore the end of the array
            // move current root (max) to end
            swap(arr,0, i);

            // heapify the reduced heap
            bubbleDown(arr, i, 0);
        }
    }

    // recursive, heapify subtree rooted at index i, heap size n
    private static void bubbleDown(int[] arr, int n, int i) {
        int largest = i;
        int left  = 2 * i + 1; // get left child index
        int right = 2 * i + 2; // get right child index

        // if there's a left child, and left child is larger, make largest the left
        if (left < n && arr[left] > arr[largest])
            largest = left;

        // if there's a right child, and the right child is larger, make largest the right
        if (right < n && arr[right] > arr[largest])
            largest = right;

        // if parent is not the largest
        if (largest != i) {
            swap(arr, i, largest);
            bubbleDown(arr, n, largest);
        }
    }

    public static void main(String[] args) {
        int[] arr = {12, 11, 13, 5, 6, 7, 0, 30, 2, 67};
        sort(arr);
        System.out.println(java.util.Arrays.toString(arr));
    }
}

Advantages of Heap Sort

  • Time complexity of \(O(n \log n)\)
  • Efficient Memory Usage (in-place, minimal extra memory)
  • Easy to understand and implement compared to other efficient sorting algorithm

Disadvantage: Heap sort is unstable.