DEV Community

Bill Odida
Bill Odida

Posted on

Understanding Heap Sort in Kotlin: A Beginner's Guide

What is HeapSort?

HeapSort is a comparison-based sorting algorithm that uses a binary heap data structure to sort elements. Developed by J. W. J. Williams in 1964, it combines the efficiency of both insertion sort and merge sort while performing sorting in-place. The algorithm works by first building a heap from the input data and then repeatedly extracting the maximum element from the heap and rebuilding it until all elements are sorted.

When to Use HeapSort?

HeapSort is particularly useful in several scenarios:

  • Limited Memory: Its in-place sorting capability makes it memory efficient.
  • Guaranteed Performance: Unlike QuickSort, HeapSort maintains O(n log n) complexity even in worst cases.
  • Priority Queue Operations: When you need to maintain a sorted sequence with frequent insertions/deletions.
  • Large Datasets: The consistent O(n log n) performance makes it reliable for large arrays.

How HeapSort Works

HeapSort operates in two main phases: heap construction and successive removal of the maximum element.

Step-by-Step Explanation

  1. Build Max Heap:

    • Convert the input array into a max heap structure.
    • Parent nodes must be greater than their children.
  2. Heap Extraction:

    • Repeatedly remove the maximum element (root).
    • Restructure the heap after each removal.
    • Place extracted elements at the end of the array.
  3. Final Array:

    • The process results in a sorted array in ascending order.

Visual Representation of HeapSort

To sort the array [64, 34, 25, 12, 22]:

  1. Build max heap:
        64
       /  \
      34   25
     /  \
    12   22
Enter fullscreen mode Exit fullscreen mode
  1. Extract maximum elements:
   Step 1: [22, 34, 25, 12, |64]
   Step 2: [12, 34, 25, |22, 64]
   Step 3: [12, 25, |34, 22, 64]
   ...
Enter fullscreen mode Exit fullscreen mode

Pseudocode for HeapSort

function heapSort(arr):
    n = length(arr)

    // Build max heap
    for i from n/2-1 down to 0:
        heapify(arr, n, i)

    // Extract elements from heap
    for i from n-1 down to 0:
        swap arr[0] with arr[i]
        heapify(arr, i, 0)

function heapify(arr, n, i):
    largest = i
    left = 2*i + 1
    right = 2*i + 2

    if left < n and arr[left] > arr[largest]:
        largest = left

    if right < n and arr[right] > arr[largest]:
        largest = right

    if largest != i:
        swap arr[i] with arr[largest]
        heapify(arr, n, largest)
Enter fullscreen mode Exit fullscreen mode

Implementing HeapSort in Kotlin

fun heapSort(arr: IntArray) {
    val n = arr.size

    // Build max heap
    for (i in n / 2 - 1 downTo 0) {
        heapify(arr, n, i)
    }

    // Extract elements from heap
    for (i in n - 1 downTo 0) {
        // Move current root to end
        arr[0] = arr[i].also { arr[i] = arr[0] }
        heapify(arr, i, 0)
    }
}

fun heapify(arr: IntArray, n: Int, i: Int) {
    var largest = i
    val left = 2 * i + 1
    val right = 2 * i + 2

    if (left < n && arr[left] > arr[largest])
        largest = left

    if (right < n && arr[right] > arr[largest])
        largest = right

    if (largest != i) {
        arr[i] = arr[largest].also { arr[largest] = arr[i] }
        heapify(arr, n, largest)
    }
}
Enter fullscreen mode Exit fullscreen mode

Code Walkthrough

  • Main Function:

    • First phase builds the max heap structure
    • Second phase extracts elements in order
    • Maintains heap property throughout
  • Heapify Function:

    • Ensures the heap property is maintained
    • Recursively adjusts sub-trees as needed
    • Compares parent with children and swaps if necessary

Visual Step-by-Step Process

Initial Array: [64, 34, 25, 12, 22]

Building Max Heap:

Step 1: [64, 34, 25, 12, 22]
        64
       /  \
      34   25
     /  \
    12   22

Step 2: After heapify
        64
       /  \
      34   25
     /  \
    12   22
Enter fullscreen mode Exit fullscreen mode

Extraction Phase:

Step 1: [22, 34, 25, 12, |64]
Step 2: [12, 34, 25, |22, 64]
Step 3: [12, 25, |34, 22, 64]
Final:  [12, 22, 25, 34, 64]
Enter fullscreen mode Exit fullscreen mode

Time Complexity Analysis

Building Heap: O(n)

  • Although it appears to be O(n log n), it's actually O(n)
  • Upper levels of the tree have more work but fewer nodes

Extraction Phase: O(n log n)

  • n elements
  • log n operations per extraction

Overall Complexity: O(n log n)

  • Consistent across all cases (best, average, worst)
  • More predictable than QuickSort

Space Complexity: O(1)

  • In-place sorting algorithm
  • Only requires constant extra space

Detailed Performance Breakdown

Number of Operations

For an array of size n:

  1. Comparisons:

    • Building heap: O(n)
    • Extraction phase: O(n log n) Total: O(n log n)
  2. Swaps:

    • Building heap: O(n)
    • Extraction phase: O(n log n) Total: O(n log n)

Performance Table

Array Size (n) Build Heap Extraction Total Operations
10 10 23 33
100 100 460 560
1,000 1,000 6,908 7,908

Optimization Techniques

  1. Bottom-up Heap Construction
fun buildHeapBottomUp(arr: IntArray) {
    val n = arr.size
    for (i in n / 2 - 1 downTo 0) {
        heapifyIterative(arr, n, i)
    }
}

fun heapifyIterative(arr: IntArray, n: Int, i: Int) {
    var current = i
    while (true) {
        val largest = current
        val left = 2 * current + 1
        val right = 2 * current + 2

        if (left < n && arr[left] > arr[largest])
            largest = left
        if (right < n && arr[right] > arr[largest])
            largest = right

        if (largest == current) break

        arr[current] = arr[largest].also { arr[largest] = arr[current] }
        current = largest
    }
}
Enter fullscreen mode Exit fullscreen mode
  1. Cache-Optimized Implementation
fun heapSortCacheOptimized(arr: IntArray) {
    val n = arr.size
    buildHeapBottomUp(arr)

    for (i in n - 1 downTo 0) {
        arr[0] = arr[i].also { arr[i] = arr[0] }
        siftDown(arr, 0, i)
    }
}
Enter fullscreen mode Exit fullscreen mode

Comparison with Other Sorting Algorithms

Algorithm Best Case Average Case Worst Case Space Stable
HeapSort O(n log n) O(n log n) O(n log n) O(1) No
QuickSort O(n log n) O(n log n) O(n²) O(log n) No
MergeSort O(n log n) O(n log n) O(n log n) O(n) Yes
InsertionSort O(n) O(n²) O(n²) O(1) Yes

Advantages and Disadvantages of HeapSort

Advantages:

  • Consistent O(n log n) performance
  • In-place sorting with O(1) extra space
  • No quadratic worst-case scenario
  • Excellent for systems with memory constraints

Disadvantages:

  • Usually slower than QuickSort in practice
  • Not stable - doesn't preserve order of equal elements
  • Poor cache performance due to non-sequential memory access
  • More complex implementation than simpler algorithms

Conclusion

HeapSort stands out for its consistent performance and memory efficiency. While it may not be the fastest sorting algorithm in practice, its guaranteed O(n log n) complexity and in-place sorting make it an excellent choice for systems with memory constraints or when consistent performance is crucial. Its unique properties also make it valuable in implementing priority queues and other specialized data structures.

Top comments (0)