Data Structures & Algorithms

Chapter 5
Sorting and Order Statistics

Bachelors Program in Full Stack Development
Data Structures & Algorithms   •   Bachelor

Sorting and Order Statistics

Introduction

Sorting rearranges records into nondecreasing or nonincreasing order according to a comparison key. It is one of the most frequently used operations in software systems because ordered data supports binary search, duplicate detection, reporting, ranking, indexing, range queries, and efficient merging. In a full-stack application, sorting may occur in a database query, an API service, a browser, or a data-processing pipeline. Choosing an inappropriate algorithm can increase latency, memory consumption, and infrastructure cost.

This chapter develops elementary comparison sorts, efficient comparison sorts, linear-time integer and distribution sorts, and algorithms for selecting the kth smallest or largest element. Analysis considers running time, auxiliary space, stability, adaptiveness, and whether the algorithm operates in place.

Learning Objectives

After studying this chapter, you should be able to:

  • explain sorting terminology and distinguish comparison from non-comparison sorting;
  • implement insertion, selection, merge, quick, and heap sort;
  • analyze best-, average-, and worst-case time complexity;
  • explain stability, adaptiveness, and in-place processing;
  • apply counting, radix, and bucket sort under appropriate assumptions;
  • justify the comparison-sorting lower bound;
  • use partitioning and selection algorithms to find order statistics;
  • choose a sorting strategy for a realistic software system.

1. Sorting as an Abstract Operation

Given an array A of n records and a key function key(·), sorting produces a permutation of A such that

key(A[0]) ≤ key(A[1]) ≤ ⋯ ≤ key(A[n−1])

The relation must normally be a total order: for any two keys x and y, the algorithm can determine whether x < y, x = y, or x > y. A sort is stable if records with equal keys retain their original relative order. Stability matters when records have several fields. For example, sorting employees by department after they have already been sorted by surname preserves surname order within each department.

A sort is in place when it uses only constant or small auxiliary storage beyond the input array. In-place does not mean that the algorithm uses no extra variables; it means that additional storage is not proportional to n. A sort is adaptive when it becomes faster on inputs that are already sorted or nearly sorted. Online algorithms can process elements as they arrive; insertion sort has this property.

The principal analysis measures are:

  • Time complexity: number of comparisons, moves, or key operations as a function of n.
  • Auxiliary space: additional memory excluding the input and, where appropriate, output storage.
  • Stability: preservation of the order of equal-key records.
  • Worst-case behavior: a guarantee for every input of size n.
  • Expected or average behavior: performance under a stated input distribution or randomized choices.

Big-O gives an asymptotic upper bound, Θ gives a tight asymptotic bound, and Ω gives an asymptotic lower bound. Constants and cache behavior still matter in practical implementations.

2. Elementary Comparison Sorts

2.1 Insertion Sort

Insertion sort maintains a sorted prefix. At step i, it removes A[i] and shifts larger prefix elements one position right until the correct position is found.

for i = 1 to n − 1
    x = A[i]
    j = i − 1
    while j ≥ 0 and A[j] > x
        A[j + 1] = A[j]
        j = j − 1
    A[j + 1] = x

The algorithm performs Θ(n²) comparisons and shifts in the average and worst cases. If the array is already sorted, the inner loop executes once per element, so the running time is Θ(n). Thus insertion sort is adaptive, stable, in place, and online. Its worst-case auxiliary space is Θ(1).

For n = 5 and input [4, 2, 5, 1, 3], the sorted prefixes are [4], [2, 4], [2, 4, 5], [1, 2, 4, 5], and finally [1, 2, 3, 4, 5]. Insertion sort is often useful for small subarrays inside hybrid algorithms.

2.2 Selection Sort

Selection sort repeatedly identifies the smallest element in the unsorted suffix and swaps it into the next output position.

for i = 0 to n − 2
    p = i
    for j = i + 1 to n − 1
        if A[j] < A[p]
            p = j
    swap A[i] and A[p]

It always performs Θ(n²) comparisons, regardless of input order, but only O(n) swaps. It is in place and not adaptive. The usual swap-based version is not stable: moving a distant minimum can cross an equal-key record. Selection sort is useful when writes are expensive and n is small, but its quadratic comparisons make it unsuitable for large data sets.

3. Divide-and-Conquer Sorting

3.1 Merge Sort

Merge sort divides the array into two halves, recursively sorts each half, and merges two sorted sequences. The merge operation scans both sequences once and places the smaller front element into an auxiliary array.

mergeSort(A, left, right)
    if left ≥ right: return
    mid = floor((left + right) / 2)
    mergeSort(A, left, mid)
    mergeSort(A, mid + 1, right)
    merge the two sorted ranges

For n elements, the recurrence is

T(n) = 2T(n/2) + Θ(n), T(1) = Θ(1)

Therefore, by the recursion-tree method or the Master Theorem, T(n) = Θ(n log n). The merge requires Θ(n) auxiliary space for an array implementation, and the recursion stack requires Θ(log n) space. Merge sort is stable when the merge chooses the left element first on equality. Its running time is insensitive to input order, so it is not adaptive in its conventional form.

Merge sort is especially appropriate for linked lists and external sorting. External sorting handles data that does not fit in main memory: sorted runs are produced, then repeatedly merged from secondary storage. Sequential access makes merge-based methods efficient for this setting.

3.2 Quick Sort

Quick sort selects a pivot, partitions the array so that elements less than the pivot precede it and elements greater than the pivot follow it, then recursively sorts the two partitions.

quickSort(A, low, high)
    if low ≥ high: return
    p = partition(A, low, high)
    quickSort(A, low, p − 1)
    quickSort(A, p + 1, high)

A partition implementation must specify how equality is handled. A common Lomuto scheme chooses A[high] as pivot, maintains an index boundary for elements ≤ pivot, and performs a final pivot swap. Choosing the final element as pivot gives the recurrence

T(n) = T(k) + T(n − k − 1) + Θ(n)

where k is the number of elements placed before the pivot. If partitions are balanced, the time is Θ(n log n). If one partition has n − 1 elements at every level, the time is Θ(n²). Randomized pivots, random shuffling, or median-of-three selection reduce the likelihood of consistently poor partitions but do not remove the deterministic worst-case bound unless a more sophisticated guarantee is used.

Quick sort is usually in place apart from recursion space, and it has excellent cache locality. Basic versions are not stable. Recursion depth is O(log n) on balanced partitions but can be O(n) in the worst case; implementations can recurse on the smaller partition first and process the larger one iteratively to limit stack use.

3.3 Heap Sort

A binary max-heap is a complete binary tree in which every parent key is at least as large as its children. In a zero-based array, the children of index i are 2i + 1 and 2i + 2 when those indices exist. Building a heap bottom-up takes Θ(n), not Θ(n log n), because most nodes are near the leaves. Repeatedly exchanging the root with the last unsorted item and restoring the heap takes Θ(log n) per extraction.

Consequently, heap sort has Θ(n log n) best-, average-, and worst-case time, Θ(1) auxiliary space, and is generally not stable. It gives a strong worst-case guarantee while remaining in place, although it often has poorer cache behavior than quick sort.

4. Non-Comparison Sorting

Comparison sorts obtain information only through comparisons. Counting, radix, and bucket sort exploit additional assumptions about keys and can therefore exceed the comparison lower bound.

4.1 Counting Sort

Suppose n keys are integers in the range 0 through k. Counting sort creates an array C[0…k], counts each key, converts counts to cumulative positions, and places records into an output array. Its time and auxiliary space are

T(n, k) = Θ(n + k)

and, for the stable output-array version, auxiliary space is Θ(n + k). It is stable when records are placed by scanning the input from right to left while using cumulative positions. Counting sort is advantageous when k is not substantially larger than n. If n = 1,000 and keys range from 0 to 1,000, the work is proportional to about 2,001 basic array positions; if keys range from 0 to 10⁹, the count array is impractical.

4.2 Radix Sort

Radix sort processes keys digit by digit. In least-significant-digit (LSD) radix sort, each pass must use a stable sort, commonly counting sort. If there are d digits and each digit has base b, the running time is

T(n, d, b) = Θ(d(n + b))

assuming fixed-width representations and constant-time digit extraction. For fixed-width machine integers, d and b may be treated as constants, yielding linear behavior in n. Signed integers require a consistent representation strategy, such as separately handling negative and nonnegative values or transforming the sign ordering. Radix sort is not a comparison sort and is stable when its digit-level sort is stable.

4.3 Bucket Sort

Bucket sort distributes values into buckets, sorts each bucket, and concatenates the buckets. For values independently and approximately uniformly distributed over an interval, the expected total work can be Θ(n), while the worst case is Θ(n²) if all values fall into one bucket and insertion sort is used within buckets. Its performance depends strongly on the distribution assumption, bucket count, and internal sorting method. It is useful for uniformly distributed floating-point values after mapping them to bucket indices, but it is not a universal replacement for comparison sorting.

5. Comparing the Main Algorithms

AlgorithmBest timeAverage timeWorst timeExtra spaceStable?Adaptive?
InsertionΘ(n)Θ(n²)Θ(n²)Θ(1)YesYes
SelectionΘ(n²)Θ(n²)Θ(n²)Θ(1)Usually noNo
MergeΘ(n log n)Θ(n log n)Θ(n log n)Θ(n)YesNo
QuickΘ(n log n)Θ(n log n) expectedΘ(n²)Θ(log n) expectedNoNo
HeapΘ(n log n)Θ(n log n)Θ(n log n)Θ(1)NoNo
CountingΘ(n + k)Θ(n + k)Θ(n + k)Θ(n + k)Yes, if implemented stablyNot generally
RadixΘ(d(n + b))Θ(d(n + b))Θ(d(n + b))depends on digit sortYes, with stable passesNot generally
BucketΘ(n) expectedΘ(n) expectedΘ(n²) typical implementationΘ(n + m)DependsDepends

Here k is the key range, d is the number of digits, b is the radix, and m is the number of buckets. “Expected” requires a stated randomization or distribution assumption.

6. The Comparison-Sorting Lower Bound

Any deterministic comparison sort can be represented by a decision tree. Each internal node represents a comparison and has at most two relevant outcomes for distinct keys: one key is smaller or the other is smaller. Sorting n distinct elements requires distinguishing among n! possible input permutations. A binary tree of height h has at most 2^h leaves, so

2h ≥ n!

Taking logarithms gives h ≥ log₂(n!)]. Since n! grows at least as fast as (n/2)^(n/2) for n ≥ 2, log₂(n!) is Ω(n log n). Therefore every comparison sort has a worst-case lower bound of Ω(n log n). Merge sort and heap sort attain this bound asymptotically. Non-comparison algorithms avoid the conclusion because they obtain information from key representations, not only pairwise comparisons.

7. Order Statistics and Selection

The ith order statistic is the ith smallest element when the data is sorted; the minimum is the first and the median is the middle. Ranking conventions must specify whether i starts at 0 or 1. This chapter uses one-based rank for order statistics.

A full sort finds the ith element in O(n log n), but selection can be faster when the other elements are not needed. The quickselect algorithm partitions around a pivot and recursively continues only in the partition containing the desired rank. With randomized pivots, its expected time is Θ(n), although its worst case is Θ(n²). It is typically in place and need not fully sort either partition.

For example, to find the fourth smallest element in [9, 2, 7, 4, 1, 8, 3], partition around 4. The elements less than 4 are [2, 1, 3], the pivot has rank 4, and the elements greater than 4 are [9, 7, 8]. Because the pivot already has the target rank, the answer is 4 without sorting either side.

A deterministic linear-time method, often called the median-of-medians algorithm, divides the input into groups of five, finds each group median, recursively finds the median of those medians, and partitions around it. The pivot guarantees that a constant fraction of elements is discarded at each level, producing the recurrence

T(n) ≤ T(⌈n/5⌉) + T(7n/10 + c) + Θ(n)

for a constant c, which solves to Θ(n). It has stronger worst-case guarantees than randomized quickselect but larger constants, so randomized selection is often preferred in ordinary applications.

8. Worked Design Example

Consider 200,000 product records. Each record contains a product code, a category from 0 through 49, a price, and a display name. The application must provide two operations: stable sorting by category while preserving existing name order, and finding the median price for analytics.

A stable counting sort is suitable for category because k = 50 is small. Its work is Θ(n + 50), and it can preserve the existing order within each category. Sorting by price is different: prices may have a large range and fractional values, so counting sort is inappropriate. If only the median is required, randomized quickselect gives expected Θ(n) time and avoids sorting all 200,000 prices. If the analytics service requires a guaranteed worst-case bound, median-of-medians provides Θ(n) selection, though with greater implementation complexity. If the complete price order is later required, an efficient comparison sort such as merge sort or an introspective hybrid sort should be used.

This example illustrates that “the fastest sorting algorithm” is not a universal concept. The correct choice depends on key representation, required output, stability, memory, data distribution, and worst-case service-level requirements.

9. Practical Implementation and Engineering Issues

Equal keys and stability. In web interfaces, stable sorting prevents records from appearing to jump unpredictably when users sort by a secondary field. Database systems may provide explicit tie-breakers; application code should not assume stability unless documented.

Large data sets. Data that exceeds memory should be processed with external merge sort or a database engine. Loading all records into a browser is inefficient and may expose sensitive data.

Recursion and adversarial input. Public-facing services should avoid predictable quick-sort pivots when attackers can supply input. Randomization, introspective sort, or a library implementation with depth safeguards is safer.

Numerical correctness. Floating-point comparisons can produce surprising ordering near equal values. If exact monetary ordering is required, use integer minor units or a decimal representation rather than binary floating-point approximations.

Parallel and distributed sorting. Large-scale systems partition data, sort partitions in parallel, and merge or redistribute them. Communication and synchronization can dominate local sorting time; asymptotic CPU complexity alone is insufficient.

Library use. Production software should normally use a well-tested standard-library sort unless a special key distribution or memory constraint justifies a custom algorithm. Tests should include empty arrays, one element, duplicates, already sorted and reverse-sorted inputs, extreme keys, and records with equal keys.

10. Case Study: Ordering an E-Commerce Product Feed

Illustrative case. A retailer’s product API returns 50,000 items. Users may sort by stock status, price, or relevance. The original implementation sorts every request with a naïve quadratic algorithm, causing slow responses during promotions.

Background and problem. Profiling shows that sorting dominates response time. Stock status has only three values, price is a decimal field, and relevance is a computed score. The product team also requires deterministic ordering when scores tie.

Analysis. Selection sort performs Θ(n²) comparisons and is unsuitable. Counting sort is appropriate for the three stock categories but cannot directly sort arbitrary decimal prices. A stable comparison sort is suitable for price and relevance. A composite key—score followed by product identifier—makes ties deterministic. If records are already approximately ordered by price, a production adaptive sort may perform particularly well.

Approach. The service first filters products, then uses a stable library sort with an explicit secondary identifier. For stock status, it uses three groups rather than a general-purpose sort. The API limits page size and uses database indexes for common persistent orderings. Median and percentile analytics are computed separately with selection or database aggregate facilities rather than sorting the complete list unnecessarily.

Outcome. The quadratic bottleneck disappears, equal-score results become reproducible, and memory use is controlled by pagination. Monitoring records latency, input size, and worst-case behavior after deployment.

Learning points. Algorithm selection must follow the data model and user requirement. Stable ordering and deterministic tie-breaking are product-quality concerns, not merely implementation details. Indexing and pagination can be more valuable than repeatedly sorting large result sets in application memory.

11. Limitations, Governance, and Contemporary Developments

Sorting can encode policy. Ranking products, applicants, or content may affect access and visibility, so developers should document the key, tie-breaking rules, and data provenance. A stable algorithm does not make a ranking fair: biased input features or opaque relevance scores can still produce discriminatory outcomes. Logs should record algorithm versions where ranking has regulatory or business significance, while personal data should be minimized and protected.

Modern language runtimes commonly use hybrid algorithms combining ideas from merge sort, insertion sort, and quick sort. Such designs exploit real-world runs, small partitions, and depth limits. Parallel sorting, SIMD-friendly partitioning, external-memory algorithms, and GPU methods continue to improve throughput for large data sets. These developments do not replace the fundamental analysis in this chapter; they add hardware, memory hierarchy, and distribution considerations.

Chapter Summary

Sorting rearranges records according to an ordered key. Insertion sort is stable, adaptive, online, and effective for small or nearly sorted inputs. Selection sort uses few swaps but remains quadratic. Merge sort provides stable Θ(n log n) performance at the cost of auxiliary memory. Quick sort is often fast and cache-efficient, with expected Θ(n log n) time but quadratic worst case unless safeguards are used. Heap sort is in place and guarantees Θ(n log n) time, though it is usually unstable.

Counting, radix, and bucket sort can achieve linear or near-linear performance by exploiting key ranges, digit representations, or distribution assumptions; these assumptions must be checked carefully. The decision-tree argument establishes an Ω(n log n) worst-case lower bound for comparison sorting. Selection algorithms avoid unnecessary full sorting: randomized quickselect has expected linear time, while median-of-medians guarantees linear worst-case time. Professional choices consider stability, adaptiveness, memory, data distribution, security, external storage, and application requirements.

Key Terms

Stable sort: A sort that preserves the relative order of equal-key records.

In-place: Using only constant or small auxiliary storage beyond the input.

Adaptive sort: A sort whose performance improves on partially ordered input.

Comparison sort: A sort that learns key order only through comparisons.

Insertion sort: A prefix-building sort that inserts each item into a sorted prefix.

Selection sort: A sort that repeatedly selects the next minimum or maximum.

Merge sort: A divide-and-conquer sort based on merging sorted subarrays.

Quick sort: A partition-based divide-and-conquer sort.

Pivot: The element or value used to divide a quick-sort or selection partition.

Partition: Rearranging elements around a pivot or boundary.

Heap: A complete-tree structure satisfying a parent–child priority relation.

Heap sort: An in-place sort based on heap construction and repeated extraction.

Counting sort: An integer-key sort based on frequency counts.

Radix sort: A digit-by-digit sort using a stable sort at each digit position.

Bucket sort: A distribution sort that places values into intervals or buckets.

Order statistic: The element occupying a specified rank in sorted order.

Quickselect: A partition-based algorithm for selecting one rank.

Decision tree: A tree representing comparison outcomes of an algorithm.

Lower bound: An asymptotic limit below which no algorithm in a specified model can perform.

External sorting: Sorting data stored partly outside main memory.

Review Questions

Short Answer Questions

  1. What does it mean for a sorting algorithm to be stable?
  2. Why is insertion sort adaptive?
  3. State the worst-case time complexity of quick sort with consistently poor pivots.
  4. Under what key-range condition is counting sort attractive?
  5. What is an order statistic?

Long Answer Questions

  1. Explain merge sort, derive its recurrence, and justify its Θ(n log n) running time.
  2. Compare quick sort and heap sort in terms of time guarantees, space, stability, and practical behavior.
  3. Explain how stable LSD radix sort works and identify the role of the digit-level sorting algorithm.
  4. Present the decision-tree argument for the Ω(n log n) comparison-sorting lower bound.
  5. Discuss the factors a full-stack developer should evaluate before choosing a sorting algorithm for an API.

Analytical / Application Questions

  1. A system sorts 2,000,000 records whose keys are integers from 0 through 99. Which algorithm would you consider first, and what assumptions and memory costs must be evaluated?
  2. An API needs only the 95th-percentile value of a large unsorted numeric array. Compare full sorting, randomized quickselect, and deterministic selection, including their time guarantees.
  3. Design a sorting strategy for a product feed requiring stable ordering by category, deterministic tie-breaking by identifier, pagination, and protection against adversarial request data. Justify each design choice.

Multiple Choice Questions with Answers

Question 1

Which property guarantees that equal-key records retain their original relative order?

A. Adaptiveness

B. Stability

C. In-place processing

D. Partitioning

Correct Answer: B. Stability

Question 2

What is the worst-case running time of insertion sort?

A. Θ(log n)

B. Θ(n)

C. Θ(n log n)

D. Θ(n²)

Correct Answer: D. Θ(n²)

Question 3

Which algorithm guarantees Θ(n log n) time in the best, average, and worst cases while using constant auxiliary space?

A. Merge sort

B. Randomized quick sort

C. Heap sort

D. Bucket sort

Correct Answer: C. Heap sort

Question 4

Counting sort is most appropriate when:

A. Keys are arbitrary objects with no order

B. The integer key range is reasonably small relative to the input size

C. The input is stored only on disk

D. All keys are distinct floating-point values

Correct Answer: B. The integer key range is reasonably small relative to the input size

Question 5

In merge sort, what is the purpose of the merge operation?

A. To choose a random pivot

B. To build a binary heap

C. To combine two sorted sequences into one sorted sequence

D. To count the frequency of each key

Correct Answer: C. To combine two sorted sequences into one sorted sequence

Question 6

For a balanced quick-sort partition, the typical running time is:

A. Θ(1)

B. Θ(log n)

C. Θ(n)

D. Θ(n log n)

Correct Answer: D. Θ(n log n)

Question 7

What is the expected running time of randomized quickselect?

A. Θ(log n)

B. Θ(n)

C. Θ(n log n)

D. Θ(n²)

Correct Answer: B. Θ(n)

Question 8

Why can comparison sorting not generally beat Ω(n log n) in the worst case?

A. Arrays cannot be accessed randomly

B. A comparison decision tree must distinguish among n! input permutations

C. Every comparison requires disk access

D. Stable sorting requires extra memory

Correct Answer: B. A comparison decision tree must distinguish among n! input permutations

Question 9

Which condition is required for LSD radix sort to preserve stability across passes?

A. Each digit pass uses a stable sorting method

B. The most significant digit is processed first only

C. All keys must be prime

D. The input must already be sorted

Correct Answer: A. Each digit pass uses a stable sorting method

Question 10

A system needs only the median and does not need the complete sorted array. Which approach usually avoids unnecessary work?

A. Selection, such as quickselect

B. Selection sort

C. Repeatedly scanning the array without tracking a rank

D. Always using merge sort

Correct Answer: A. Selection, such as quickselect

Data Structures & Algorithms
Chapter 5 of 15