Non-Comparison Linear Time Sorts
Breaking the comparison barrier: Counting Sort prefix-sum stability, Radix Sort digit-by-digit passes, and Bucket Sort uniform hashing.
Topics Covered:
56. Counting Sort (Frequency Hashing & Stable Reconstruction) • 57. Radix Sort (LSD vs MSD Positional Sorting) • 58. Bucket Sort (Uniform Distribution & Scatter-Gather)
Comparison-based sorting algorithms are fundamentally bound by the decision tree lower bound. Non-comparison sorting algorithms—Counting Sort, Radix Sort, and Bucket Sort—bypass this mathematical barrier by exploiting algebraic and structural properties of the input keys rather than comparing relative magnitudes. By leveraging bounded integer ranges, positional digit decompositions, and continuous probability distributions, these algorithms achieve linear execution time. This chapter explores frequency histograms, prefix sum address calculation, multi-pass positional stability, bitwise radix extraction, and probabilistic scatter-gather architectures.
Learning Objectives
#- Formulate how frequency histograms and cumulative prefix sums establish exact target indices in Counting Sort.
- Implement backward stable array reconstruction in Counting Sort and prove why backward traversal is mandatory for stability.
- Differentiate Least Significant Digit (LSD) from Most Significant Digit (MSD) Radix Sort, proving why LSD requires a stable sub-sorter.
- Analyze Radix Sort base selection trade-offs ( vs ) for high-throughput 32-bit and 64-bit integer sorting.
- Formulate the scatter-sort-gather pipeline of Bucket Sort and prove its expected linear runtime under a continuous uniform distribution .
Topic 56: Counting Sort (Frequency Hashing & Cumulative Prefix Sums)
#1. The Direct-Indexing Paradigm
#Counting Sort assumes that each of the input elements is an integer in the bounded range . Rather than performing comparisons, it determines for each input element how many elements in the input are strictly smaller than . With this count in hand, can be placed directly into its final position in the output array.
The algorithm operates in three distinct phases:
- Histogram Construction: Compute a frequency histogram array where records the occurrences of value in input array .
- Cumulative Prefix Sums: Transform in place such that each cell stores . The value indicates the total count of elements , which defines the upper boundary of index positions where value belongs in the output.
- Backward Stable Placement: Traverse the original array backwards from index down to . For each element , place it at index in output buffer , and decrement .
2. Frequency & Cumulative Prefix Trace
#Consider sorting the array of size with maximum key :
| Key Value () | Raw Frequency () | Cumulative Count () | Reserved Output Indices in | Algorithmic Significance |
|---|---|---|---|---|
0 | 0 | 0 | None | No zero elements |
1 | 1 | 1 | Index 0 | Single instance of 1 |
2 | 2 | 3 | Indices 1, 2 | Two instances of 2 |
3 | 2 | 5 | Indices 3, 4 | Two instances of 3 |
4 | 1 | 6 | Index 5 | Single instance of 4 |
5 | 0 | 6 | None | Value absent |
6 | 0 | 6 | None | Value absent |
7 | 0 | 6 | None | Value absent |
8 | 1 | 7 | Index 6 | Single instance of 8 |
3. Backward Stable Placement Trace
#Traversing from right to left ( down to ):
| Step | Inspected Value | Current | Output Index () | Updated | Output Array State |
|---|---|---|---|---|---|
6 | 1 | 1 | |||
5 | 5 | ||||
4 | 4 | ||||
3 | 8 | 7 | |||
2 | 3 | ||||
1 | 2 | ||||
0 | 4 | 6 |
Why Backward Traversal is Mandatory for Stability:
In step 5, duplicate (appearing later in ) was assigned output index 4. In step 4, duplicate (appearing earlier in ) was assigned output index 3. Because occupies a smaller index than , their relative original order is strictly preserved. Traversing forward would place at 4 and at 3, inverting their order and destroying stability.
4. Canonical Algorithm: Stable Counting Sort
#FUNCTION CountingSort(A: Array of Integer, n: Integer, k: Integer) -> Array of Integer:
// Allocate count buffer of size k + 1 and output buffer of size n
count <- Array of size (k + 1) initialized to 0
output <- Array of size n
// Phase 1: Build frequency histogram
FOR i <- 0 TO n - 1 DO
count[A[i]] <- count[A[i]] + 1
END FOR
// Phase 2: Compute cumulative prefix sums
FOR j <- 1 TO k DO
count[j] <- count[j] + count[j - 1]
END FOR
// Phase 3: Place elements backwards into output buffer
FOR i <- n - 1 DOWNTO 0 DO
val <- A[i]
targetIdx <- count[val] - 1
output[targetIdx] <- val
count[val] <- count[val] - 1
END FOR
RETURN outputComplexity & Domain Constraints:
- Time Complexity: across all cases (best, average, worst). Building histogram takes , prefix sums take , and output placement takes .
- Auxiliary Space: for the count array of size and output buffer of size .
- Operational Boundary: Counting Sort is asymptotically optimal when , yielding time. If (e.g., sorting 10 integers where maximum value is ), the memory and runtime overhead makes it far worse than standard algorithms.
Topic 57: Radix Sort (Positional Digit Sorting)
#1. Positional Decomposition & The Stability Invariant
#When integer keys span a wide numerical range where , Counting Sort becomes impractical. Radix Sort overcomes this by decomposing each key into digits evaluated in a specific positional numerical base :
- LSD (Least Significant Digit) Radix Sort: Sorts keys starting from the least significant digit (units position) toward the most significant digit (highest power of ).
- MSD (Most Significant Digit) Radix Sort: Sorts keys starting from the highest power of toward the units digit, recursively partitioning elements into sub-buckets (similar to a Trie or QuickSort).
The Fundamental LSD Theorem:
If an array is sorted by digit using an unconditionally stable sorting subroutine, then for any two keys whose digits at positions are identical, their relative sorted order from previous digit passes is strictly preserved. Therefore, sorting passes from least significant digit () to most significant digit () yields a globally sorted array.
2. Multi-Pass LSD State Progression
#Consider sorting eight 3-digit decimal numbers ():
| Key Identifier | Pass 1: Units Digit () | Pass 1 Sorted State | Pass 2: Tens Digit () | Pass 2 Sorted State | Pass 3: Hundreds () | Final Globally Sorted State |
|---|---|---|---|---|---|---|
0 | 170 | 7 | 002 | 1 | 002 (2) | |
5 | 090 | 4 | 802 | 0 | 024 (24) | |
5 | 002 | 7 | 024 | 0 | 045 (45) | |
0 | 802 | 9 | 045 | 0 | 066 (66) | |
2 | 024 | 0 | 066 | 0 | 075 (75) | |
4 | 045 | 2 | 170 | 0 | 090 (90) | |
2 | 075 | 0 | 075 | 8 | 170 | |
6 | 066 | 6 | 090 | 0 | 802 |
Notice that in Pass 2, and both have tens digit . Because the digit sort is stable, remains before as established in Pass 1. In Pass 3, sorting by the hundreds digit places all numbers beginning with in front, perfectly ordered by their lower digits.
3. Canonical Algorithm: LSD Radix Sort
#FUNCTION RadixSort(A: Array of Integer, n: Integer):
maxVal <- FindMaximum(A, n)
// Execute stable counting sort for each digit position: 1, 10, 100...
exp <- 1
WHILE FLOOR(maxVal / exp) > 0 DO
CountingSortByDigit(A, n, exp)
exp <- exp * 10
END WHILE
FUNCTION CountingSortByDigit(A: Array of Integer, n: Integer, exp: Integer):
output <- Array of size n
count <- Array of size 10 initialized to 0
// Count occurrences of current digit: (A[i] / exp) % 10
FOR i <- 0 TO n - 1 DO
digit <- FLOOR(A[i] / exp) MOD 10
count[digit] <- count[digit] + 1
END FOR
// Prefix sums
FOR j <- 1 TO 9 DO
count[j] <- count[j] + count[j - 1]
END FOR
// Build output array backwards to guarantee stability
FOR i <- n - 1 DOWNTO 0 DO
digit <- FLOOR(A[i] / exp) MOD 10
output[count[digit] - 1] <- A[i]
count[digit] <- count[digit] - 1
END FOR
// Copy sorted output back to array A
FOR i <- 0 TO n - 1 DO
A[i] <- output[i]
END FOR4. Asymptotic Complexity & High-Performance Radix Choice
#The total runtime of LSD Radix Sort across keys with maximum value in base is:
Hardware-Optimized Base Selection:
When sorting 32-bit unsigned integers:
- Choosing decimal base requires passes, and integer division/modulo instructions (
/and%) which are computationally expensive on CPUs. - Choosing base (1 byte per digit) allows digits to be extracted using instant bitwise shifts and bitmasks:
- Number of passes: passes.
- Frequency buffer size per pass: integers (fits effortlessly into L1 CPU cache).
- Total runtime: .
On modern hardware, a 4-pass 8-bit Radix Sort routinely outperforms Quicksort by a factor of to when sorting millions of 32-bit integers.
Topic 58: Bucket Sort (Uniform Scatter-Gather Partitioning)
#1. The Scatter-Gather Architecture
#Bucket Sort assumes that the input data is generated by a random process that distributes elements uniformly and independently over the continuous real interval .
The algorithm proceeds through three phases:
- Scatter: Divide into equal-width sub-intervals (buckets) of size . For each element , map it to bucket index and insert it into a dynamic linked list or resizable array at
buckets[b]. - Sort: Sort each individual bucket independently using Insertion Sort.
- Gather: Concatenate all sorted buckets in order from bucket to to form the final sorted array.
2. State Progression: Distributing Keys into Buckets
#Consider sorting floating-point values:
| Bucket Index () | Continuous Range | Raw Scattered Elements () | Sorted Bucket State (Insertion Sort) | Gather Order |
|---|---|---|---|---|
0 | — | |||
1 | Indices 0, 1 | |||
2 | Indices 2, 3, 4 | |||
3 | Index 5 | |||
4 | — | |||
5 | — | |||
6 | Index 6 | |||
7 | Indices 7, 8 | |||
8 | — | |||
9 | Index 9 |
Final concatenated array: .
3. Mathematical Proof: Expected Linear Runtime
#Let be a random variable denoting the number of elements placed into bucket . Since each element has an equal probability of landing in any given bucket, follows a Binomial distribution .
The time required to sort bucket via Insertion Sort is . The total time across all buckets is:
For a Binomial random variable with :
- Mean:
- Variance:
- Second Moment:
Substituting this into the total expected time summation:
Under the assumption of uniform distribution, the expected runtime of Bucket Sort is strictly linear .
Failure Mode & Degeneracy:
If the input data is severely skewed (e.g., all elements share identical values and collapse into a single bucket), Insertion Sort must process all elements in that single bucket, degrading total runtime to .
Master Comparison Matrix: Non-Comparison Linear Sorts
#| Metric | Counting Sort | Radix Sort (LSD) | Bucket Sort |
|---|---|---|---|
| Domain Constraint | Small bounded integers | Integers / fixed-length strings | Uniform floating-point numbers |
| Best-Case Time | |||
| Average-Case Time | (under uniform distribution) | ||
| Worst-Case Time | (skewed inputs) | ||
| Auxiliary Space | |||
| Stability | Stable (backward pass) | Stable (mandatory) | Stable (if bucket sorter is stable) |
| Comparison-Based? | No | No | Hybrid (Insertion Sort within buckets) |
| Primary Industry Role | Small key alphabets, sub-sorter | 32/64-bit integers, suffix arrays | Geospatial coordinates, probability floats |
Module 03 Summary & Key Takeaways
#- Circumventing Comparison Bounds: Non-comparison sorts bypass the information-theoretic lower bound by using direct address indexing, digit extraction, or statistical partitioning.
- Counting Sort Invariant: Cumulative prefix sums translate element frequencies into exact output array address bounds. Backward traversal from down to is strictly required to preserve stability.
- Radix Sort Multi-Pass Stability: LSD Radix Sort requires an unconditionally stable sub-sorter so that decisions made on lower-significance digits remain intact during higher-significance passes.
- Byte-Level Radix Efficiency: Configuring Radix Sort with base processes 32-bit integers in exactly 4 passes using efficient bitwise shifts and bitmasks without floating-point division.
- Bucket Sort Distribution Dependence: Bucket Sort achieves average-case linear time only when keys follow a uniform probability distribution over continuous intervals; clustering or skewed distributions degrades performance to quadratic .
References & Academic Attribution
#- Seward, H. H. (1954). Information sorting in the application of electronic digital computers to business operations (First formal description of Counting Sort and Radix Sort). Master's thesis, Massachusetts Institute of Technology.
- Knuth, D. E. (1998). The Art of Computer Programming, Volume 3: Sorting and Searching (2nd ed.), Section 5.2.5: Sorting by Distribution. Addison-Wesley.
- Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (2022). Introduction to Algorithms (4th ed.), Chapter 8: Sorting in Linear Time (Counting Sort, Radix Sort, Bucket Sort). MIT Press.
- Sedgewick, R., & Wayne, K. (2011). Algorithms (4th ed.), Section 5.1: String Sorts (LSD and MSD Radix Sorting). Addison-Wesley.