Quiz 2

08. Sorting Algorithms Comparison — Heap Sort, Counting Sort, Radix Sort

1636 words
8 min read
Python Week 1: the first filter for runtime behavior
Visual companion
Python
Type and operator map

Python Week 1: the first filter for runtime behavior

View
Revision summary

What this note is really saying

Short form

# 08. Sorting Algorithms Comparison — Heap Sort, Counting Sort, Radix Sort > **What problem does this solve?** You now know three sorting algorithms (selection, insertion, merge, quick).

08. Sorting Algorithms Comparison — Heap Sort, Counting Sort, Radix Sort

What problem does this solve? You now know three sorting algorithms (selection, insertion, merge, quick). But there's a whole zoo of sorting algorithms, each with unique strengths. Heap sort guarantees O(n log n) without extra memory. Counting and radix sort break the O(n log n) barrier for integer data. This module helps you choose the right tool.

1. Heap Sort — Guaranteed O(n log n), In-Place

Mental Model

Think of Heap Sort as Selection Sort's smarter cousin. Selection Sort scans the entire unsorted portion to find the maximum — O(n) per scan, O(n²) total. Heap Sort uses a binary heap to find the maximum in O(log n) time. Key insight: Build a max-heap from the array (O(n)), then repeatedly extract the maximum and place it at the end.
Full heap details are in [18-heaps-priority-queues.md] and [19-heap-sort.md].

Implementation

python
# runnable
def heap_sort(arr):
    """Sort arr in-place using heap sort.
    Time: O(n log n) — always (best, average, worst)
    Space: O(1) — in-place
    Not stable
    """
    n = len(arr)
    # Step 1: Build max-heap (heapify entire array)
    for i in range(n // 2 - 1, -1, -1):
        _sift_down(arr, n, i)
    # Step 2: Extract elements one by one
    for i in range(n - 1, 0, -1):
        arr[0], arr[i] = arr[i], arr[0]  # Swap max to end
        _sift_down(arr, i, 0)            # Restore heap on reduced array
    return arr
def _sift_down(arr, n, i):
    """Sift down element at index i in heap of size n."""
    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:
        arr[i], arr[largest] = arr[largest], arr[i]
        _sift_down(arr, n, largest)
# Test
arr = [12, 11, 13, 5, 6, 7]
print(f"Heap sort: {heap_sort(arr)}")

2. Counting Sort — O(n + k) for Integers

Mental Model

You have a pile of exam papers graded 0-100. Instead of comparing papers, you just count how many papers got each score. Then you know exactly where each paper belongs in the sorted output. Limitation: Only works for integer data within a known, small range. (Diagram)

Implementation

python
# runnable
def counting_sort(arr):
    """Sort arr (non-negative integers) in O(n + k) time.
    Time: O(n + k) where k = max(arr) - min(arr)
    Space: O(n + k)
    Stable: Yes (when using cumulative counts)
    """
    if not arr:
        return arr
    k = max(arr)  # Range of values
    # Step 1: Count occurrences
    count = [0] * (k + 1)
    for num in arr:
        count[num] += 1
    # Step 2: Compute cumulative counts (prefix sums)
    for i in range(1, k + 1):
        count[i] += count[i - 1]
    # Step 3: Build output (stable — traverse input in reverse)
    output = [0] * len(arr)
    for num in reversed(arr):
        output[count[num] - 1] = num
        count[num] -= 1
    return output
# Test
arr = [4, 2, 2, 8, 3, 3, 1]
print(f"Counting sort: {counting_sort(arr)}")
# Output: [1, 2, 2, 3, 3, 4, 8]
# Step-by-step trace
arr = [4, 2, 2, 8, 3, 3, 1]
k = 8
# After counting: count = [0, 1, 2, 2, 1, 0, 0, 0, 1]
# After cumulative: count = [0, 1, 3, 5, 6, 6, 6, 6, 7]
# Count now means: "position of last occurrence of value i in sorted output"
# For value 4: count[4] = 6, so 4 goes at position 5 (0-indexed)

Complexity

MeasurementValue
Time(O(n + k)) where k = range of values
Space(O(n + k))
StableYes
In-placeNo
When to useSmall range of integers (k < n log n)

3. Radix Sort — Sorting Digit by Digit

Mental Model

Imagine sorting dates: first by day, then by month, then by year. Each pass sorts on one digit using a stable sort (like counting sort). After sorting the most significant "digit," the array is fully sorted. For integers: Sort by least significant digit first, then tens, then hundreds... (Diagram)

Implementation

python
# runnable
def counting_sort_by_digit(arr, exp):
    """Sort arr by digit at position 10^exp using counting sort."""
    n = len(arr)
    output = [0] * n
    count = [0] * 10  # 10 possible digits (0-9)
    # Count occurrences of each digit
    for num in arr:
        digit = (num // exp) % 10
        count[digit] += 1
    # Cumulative counts
    for i in range(1, 10):
        count[i] += count[i - 1]
    # Build output (reverse for stability)
    for num in reversed(arr):
        digit = (num // exp) % 10
        output[count[digit] - 1] = num
        count[digit] -= 1
    # Copy back
    for i in range(n):
        arr[i] = output[i]
def radix_sort(arr):
    """Sort arr using radix sort (LSD first).
    Time: O(d * (n + k)) where d = number of digits, k = 10
    Space: O(n + k)
    Stable: Yes (uses stable counting sort internally)
    """
    if not arr:
        return arr
    # Find maximum to know number of digits
    max_val = max(arr)
    # Do counting sort for every digit
    exp = 1
    while max_val // exp > 0:
        counting_sort_by_digit(arr, exp)
        exp *= 10
    return arr
# Test
arr = [170, 45, 75, 90, 802, 24, 2, 66]
print(f"Radix sort: {radix_sort(arr)}")
# Output: [2, 24, 45, 66, 75, 90, 170, 802]

Complexity

MeasurementValue
Time(O(d \cdot (n + k))) where d = digits, k = base (10)
Typically(O(n \cdot \log_{k} \text{max}))
Space(O(n + k))
StableYes
In-placeNo

4. Comprehensive Sorting Comparison

AlgorithmBestAverageWorstSpaceStableIn-PlaceWhen to Use
Selection(O(n^2))(O(n^2))(O(n^2))(O(1))NoYesFew swaps, small data
Insertion(O(n))(O(n^2))(O(n^2))(O(1))YesYesNearly sorted, online
Merge(O(n\log n))(O(n\log n))(O(n\log n))(O(n))YesNoGuaranteed performance
Quick(O(n\log n))(O(n\log n))(O(n^2))(O(\log n))NoYesGeneral purpose (in-place)
Heap(O(n\log n))(O(n\log n))(O(n\log n))(O(1))NoYesGuaranteed + in-place
Counting(O(n+k))(O(n+k))(O(n+k))(O(n+k))YesNoSmall integer range
Radix(O(dn))(O(dn))(O(dn))(O(n))YesNoFixed-width integers

Decision Tree

(Diagram)

5. The Sorting Stability Concept

What is Stability?

A stable sort preserves the relative order of equal elements.
python
# runnable
# Sort by name, then by grade (stable second sort keeps name order)
students = [
    ("Alice", 85),
    ("Bob", 92),
    ("Charlie", 85),
    ("Diana", 92),
]
# Stable sort by grade
# If stable: (Alice, 85) stays before (Charlie, 85)
#            (Bob, 92) stays before (Diana, 92)

Which Sorts Are Stable?

StableNot Stable
Insertion SortSelection Sort
Merge SortQuick Sort
Counting SortHeap Sort
Radix SortShell Sort

6. When Each Sort Works Best — Decision Problems

Q1. You're sorting a list of 10 million floating-point numbers on a memory-constrained device. → Heap Sort — guaranteed O(n log n), in-place. Q2. You're sorting a list of 1 million student records by roll number (integers from 1 to 100,000). → Counting Sort — O(n + k) where k = 100,000. Q3. You need to sort a linked list of 50,000 elements. → Merge Sort — doesn't need random access; merge works perfectly with linked lists. Q4. You're implementing a sort function for a library that will be used on unknown data. → Quick Sort with median-of-three — best average performance, or Tim Sort (Python's built-in sort). Q5. You have 20 numbers and want the simplest possible code. → Insertion Sort — trivial, and O(n²) for n=20 is fine.

Practice Questions

Q1. For each algorithm, give one scenario where it's the best choice: (a) Selection Sort (b) Insertion Sort (c) Merge Sort (d) Quick Sort (e) Heap Sort (f) Counting Sort (g) Radix Sort Q2. Why is heap sort not stable? Give a concrete example. Q3. What's the time complexity of radix sort for sorting 1000 32-bit integers? Q4. Explain why counting sort requires the input to be integers. Q5. Python's built-in sort() uses TimSort — a hybrid of merge sort and insertion sort. Why would insertion sort be useful here? Q6. For n = 100 and k = 10⁶, would you use counting sort? Why or why not? Q7. Show that heap sort's build-heap phase is O(n), not O(n log n). Q8. You have an array where 99% of elements are already sorted. Which sort is best? Q9. Which sorts are comparison-based? Which are not? Q10. Design a sort for exam marks (0-100) for 10,000 students. Which algorithm?
Answers
A1. (a) When swaps are expensive. (b) Nearly sorted data or online sorting. (c) Guaranteed O(n log n) needed, or linked list. (d) General purpose with random access. (e) Guaranteed O(n log n) with limited memory. (f) Integer data with small range. (g) Fixed-width integer keys.
A2. Heap sort swaps the root with the last element (long distance). Example: [5, 3, 3'] with max-heap. 5 swaps with 3', then sift-down swaps 3 and 3' past each other.
A3. 32-bit = 32 bits. In base-2 radix sort: d = 32, n = 1000, k = 2. O(32 × 1000) = O(32,000) operations. Very fast!
A4. Counting sort uses array indices as keys. Non-integer values can't be used as indices.
A5. Insertion sort is excellent for small subproblems (its O(n²) cost is negligible for small n). TimSort uses insertion sort for small chunks (typically ≤ 64 elements).
A6. No. k = 10⁶ is much larger than n log n (100 × ~7 = 700). The O(n + k) = O(10⁶) counting sort is slower than O(n log n) comparison sort.
A7. Build-heap calls sift-down on the bottom n/2 elements. Each sift-down takes O(h) where h varies. The sum of heights is O(n). Formal proof: The total cost is (\sum_{h=0}^{\log n} \frac{n}{2^{h+1}} \cdot O(h) = O(n)).
A8. Insertion Sort — O(n) on nearly sorted data.
A9. Comparison-based: Selection, Insertion, Merge, Quick, Heap. Non-comparison: Counting, Radix, Bucket sort.
A10. Counting Sort. Range k = 101 (marks 0-100), n = 10,000. O(n + k) = O(10,101) — very fast. Join Discord Previous07. Quick Sort — The Fastest Practical SortNext09. Linked Lists — Singly, Doubly, Circular
Document outline

Keep your place and jump directly to a heading.

Table of Contents
System Normal // Awaiting Context

Intelligence Hub

Navigate the knowledge graph to generate context. The Hub adapts dynamically to surface backlinks, related notes, and metadata insights.