DSA DSA In Depth
0%
lesson quicksortrandomized-algorithmsprobability

Randomized Quicksort: Indicator Random Variables & 1.386 N ln N Proof

7 min read Module 2.2: Sorting Mechanics, Hybrid Sorts & Linear-Time Order Statistics Updated Oct 23, 2026
📄 Cheat Sheet
MIT 6.046J Lecture 4: Quicksort & Randomized Quicksort Expected O(N log N) Proof
MIT OpenCourseWare (Charles Leiserson) MIT 6.046J Lecture 4: Quicksort & Randomized Quicksort Expected O(N log N) Proof Watch on YouTube ↗

Charles Leiserson derives Hoare/Lomuto partitioning and proves the exact 2N ln N ≈ 1.386 N log₂ N expected comparison bound via indicator random variables X_ij.

0:00 / --:--

Why Randomize Pivot Selection?

Any deterministic Quicksort that picks a fixed index ($A[lo]$, $A[hi]$, or even $A[mid]$) has inputs for which every partition is $0 : (n-1)$, requiring $\sum_{k=1}^{n-1} k = \frac{n(n-1)}{2} = \Theta(n^2)$ comparisons. Randomized Quicksort defeats fixed-input adversaries by either shuffling the array upfront (Knuth-Fisher-Yates) or picking a pivot index uniformly at random from $\{lo, \dots, hi\}$ at every recursive step. The expected runtime then holds over the algorithm's internal random coin flips for every input array of $n$ distinct elements.

Theorem (Exact Expected Comparisons of Randomized Quicksort)

Let $z_1 < z_2 < \dots < z_n$ denote the $n$ distinct elements of the input array in sorted order (by rank). Define the indicator random variable: $$X_{ij} = \mathbb{I}\{\text{elements } z_i \text{ and } z_j \text{ are compared at any point during Randomized Quicksort}\} \quad (1 \le i < j \le n)$$ Because every comparison in Quicksort compares some element against the current pivot, and the pivot is never included in either recursive call, any pair $(z_i, z_j)$ is compared at most once. Thus the total number of comparisons is $X = \sum_{i=1}^{n-1}\sum_{j=i+1}^{n} X_{ij}$.

Crucial Observation: Consider the set of elements $Z_{ij} = \{z_i, z_{i+1}, \dots, z_j\}$ of cardinality $j - i + 1$. So long as no pivot is chosen from $Z_{ij}$, the entire set $Z_{ij}$ remains together in the same recursive subarray (either all strictly less than the pivot, or all strictly greater than the pivot). The very first time a pivot $p$ is chosen from $Z_{ij}$:

  • If $p = z_i$ or $p = z_j$, then $z_i$ and $z_j$ are compared during that partition pass ($X_{ij} = 1$).
  • If $z_i < p < z_j$, then $z_i$ is placed in the left partition ($< p$) and $z_j$ is placed in the right partition ($> p$); they are permanently separated and will never be compared ($X_{ij} = 0$).

Since each of the $j - i + 1$ elements in $Z_{ij}$ is equally likely to be the first one chosen as a pivot from $Z_{ij}$:

$$\Pr[X_{ij} = 1] = \frac{2}{j - i + 1}$$

Summing the Harmonic Series: Where $1.386\,n \log_2 n$ Comes From

By linearity of expectation (which holds even though the indicator variables $X_{ij}$ are not mutually independent!):

$$\mathbb{E}[X] = \sum_{i=1}^{n-1} \sum_{j=i+1}^{n} \frac{2}{j - i + 1}$$

Substitute the span length $k = j - i$ (so $k$ ranges from $1$ to $n - 1$, and there are $n - k$ pairs with span $k$):

$$\mathbb{E}[X] = \sum_{k=1}^{n-1} (n - k) \frac{2}{k + 1} = 2(n + 1)\sum_{k=1}^{n-1}\frac{1}{k + 1} - 2(n - 1) = 2(n + 1)(H_n - 1) - 2(n - 1) = 2(n + 1)H_n - 4n$$

Using the harmonic number expansion $H_n = \sum_{m=1}^n \frac{1}{m} = \ln n + \gamma + O(1/n)$ and converting from natural log to base-2 log ($\ln n = \ln(2) \cdot \log_2 n \approx 0.69315 \log_2 n$):

$$\mathbb{E}[X] \approx 2n \ln n = (2 \ln 2)\, n \log_2 n \approx 1.3863\, n \log_2 n$$

Notice how remarkably close this is to the information-theoretic optimum: Randomized Quicksort performs only $38.6\%$ more comparisons than Merge Sort ($1.0\, n \log_2 n$), and with Median-of-Three pivot selection, the constant drops further to $\frac{12}{7} \ln(2)\, n \log_2 n \approx 1.188\, n \log_2 n$!

Sorting Arena: 3-Way QuickSort, MergeSort & LSD RadixSort
O(N log N) vs O(d·N)

Step through Bentley-McIlroy 3-way partition QuickSort, Bottom-Up MergeSort inversion counting, and non-comparison LSD RadixSort with live comparison and swap counters.

Step 1 / 7
Custom Input:
Timeline

Invariant & State

3-Way QuickSort (Dijkstra Dutch National Flag): partitions subarray into < pivot, == pivot, and > pivot.
🌳 Live 2D Hierarchical Tree Topology — Sorting Arena: 3-Way QuickSort, MergeSort & LSD RadixSort Visited/Active Pivot/Root Resolved
38 [0] 27 [1] 43 [2] 3 [3] 9 [4] 82 [5] 10 [6] 27 [7]
[0]38
[1]27
[2]43
[3]3
[4]9
[5]82
[6]10
[7]27
Comparisons
0
Swaps
0
Locked Keys
0 / 8
Array Size
8
[Step 1] Starting 3-Way QuickSort on [38, 27, 43, 3, 9, 82, 10, 27].
Pivot Strategy Expected Comparisons ($\log_2 n$) Expected Swaps (Hoare) High-Probability Tail Bound
Uniform Random Pivot ($1$-sample) $2 \ln(2)\, n \log_2 n \approx 1.386\, n \log_2 n$ $\frac{1}{3} \ln(2)\, n \log_2 n \approx 0.231\, n \log_2 n$ $\Pr[X > 4n \ln n] \le n^{-2}$
Random Median-of-3 ($3$-sample) $\frac{12}{7} \ln(2)\, n \log_2 n \approx 1.188\, n \log_2 n$ $\frac{2}{7} \ln(2)\, n \log_2 n \approx 0.198\, n \log_2 n$ $\Pr[T > c n \log n] \le n^{-c'}$
Tukey's Ninther ($9$-sample) $\approx 1.09\, n \log_2 n$ $\approx 0.175\, n \log_2 n$ Sub-exponential tail concentration
Merge Sort (Reference) $1.000\, n \log_2 n - O(n)$ $1.000\, n \log_2 n$ element moves Deterministic $\le n \lceil \log_2 n \rceil$
Rank-Ordered Set Z_ij = {z_i, ..., z_j}: Only the First Pivot Chosen Inside Z_ij Decides X_ij z_1 .. z_{i-1} z_i (Pivot!) Interior {z_{i+1} ... z_{j-1}} → Splits z_i | z_j Forever! z_j (Pivot!) z_{j+1} .. z_n • Green Endpoints (2 choices out of j - i + 1): z_i and z_j ARE compared during this partition ⇒ Pr[X_ij = 1] = 2 / (j - i + 1) • Red Interior (j - i - 1 choices): Pivot p separates z_i < p < z_j into disjoint recursive calls ⇒ X_ij = 0 • Exterior Grey: Pivots outside Z_ij keep all of Z_ij together in the same recursive call (neither compares nor separates z_i, z_j).
flowchart LR A["Pick Random Pivot p ∈ [lo..hi]"] --> B{"Is Split within Middle 50% (1/4 : 3/4)?"} B -- "Yes (Pr = 1/2): 'Good' Split" --> C["Max Subproblem ≤ 3N/4 At most log_{4/3}(N) ≈ 2.41 log2(N) Good Splits along any root-to-leaf path!"] B -- "No (Pr = 1/2): 'Bad' Split" --> D["Absorbed into Expected Length ≤ 2× Good Splits via Chernoff Bound"]
Hardware Reality: Fast Unbiased Random Range Generation (Lemire's Multiply-Shift)

Calling std::rand() % len inside a Quicksort recursion loop has two flaws: (1) 64-bit integer modulo (DIV) takes 20–40 cycles, and (2) rand() % len introduces modulo bias when len does not divide $2^{32}$. Production randomized algorithms pair a fast register-resident PRNG (such as SplitMix64 or xoshiro256**, ~2 cycles) with Daniel Lemire's Fast Range reduction: (uint64_t(rng32()) * uint64_t(len)) >> 32, mapping a 32-bit random word into $[0, len)$ in a single 3-cycle multiply-high instruction without division!

Production C++20 Implementation: Randomized Quicksort & Empirical $2(n+1)H_n - 4n$ Verifier

#include <cmath>
#include <cstddef>
#include <cstdint>
#include <utility>
#include <vector>

struct SplitMix64 {
    std::uint64_t state;
    explicit constexpr SplitMix64(std::uint64_t seed = 0x9E3779B97F4A7C15ULL) noexcept : state(seed) {}

    constexpr std::uint64_t next() noexcept {
        std::uint64_t z = (state += 0x9E3779B97F4A7C15ULL);
        z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ULL;
        z = (z ^ (z >> 27)) * 0x94D049BB133111EBULL;
        return z ^ (z >> 31);
    }

    // Lemire's multiply-shift into [0, bound)
    constexpr std::size_t uniform(std::size_t bound) noexcept {
        return static_cast<std::size_t>((__uint128_t(next()) * __uint128_t(bound)) >> 64);
    }
};

// Instrumented Randomized Quicksort counting exact key comparisons
template <typename T>
std::uint64_t randomized_quicksort_count(T* a, std::ptrdiff_t lo, std::ptrdiff_t hi, SplitMix64& rng) noexcept {
    if (lo >= hi) return 0;

    const std::ptrdiff_t len = hi - lo + 1;
    const std::ptrdiff_t piv_idx = lo + static_cast<std::ptrdiff_t>(rng.uniform(static_cast<std::size_t>(len)));
    std::swap(a[piv_idx], a[hi]);
    const T pivot = a[hi];

    std::uint64_t cmps = 0;
    std::ptrdiff_t i = lo;
    for (std::ptrdiff_t j = lo; j < hi; ++j) {
        ++cmps;
        if (a[j] < pivot) {
            std::swap(a[i++], a[j]);
        }
    }
    std::swap(a[i], a[hi]);

    cmps += randomized_quicksort_count(a, lo, i - 1, rng);
    cmps += randomized_quicksort_count(a, i + 1, hi, rng);
    return cmps;
}

// Exact theoretical expectation E[X] = 2(n + 1)*H_n - 4n for n distinct keys
inline double theoretical_expected_comparisons(std::size_t n) noexcept {
    double h_n = 0.0;
    for (std::size_t k = 1; k <= n; ++k) {
        h_n += 1.0 / static_cast<double>(k);
    }
    return 2.0 * static_cast<double>(n + 1) * h_n - 4.0 * static_cast<double>(n);
}
Las Vegas vs. Monte Carlo & Why Introsort Still Adds a Depth Cap

Randomized Quicksort is a Las Vegas randomized algorithm: its output is always 100% sorted, and only its running time is a random variable. While Chernoff bounds guarantee $\Pr[X > 4n \ln n] \le n^{-2}$, in safety-critical real-time systems or deterministic standard library APIs (where a global PRNG state cannot be mutated inside std::sort), David Musser's Introsort (1997) tracks recursion depth and deterministically switches to Heapsort if depth ever exceeds $2\lfloor\log_2 n\rfloor$, upgrading expected $O(n \log n)$ into strict worst-case $O(n \log n)$ with zero extra memory.

Check yourself Question 1 of 4
Concept Check Probability That Two Rank-Ordered Elements Are Compared in Randomized Quicksort recall

Let $z_1 < z_2 < \dots < z_n$ be the elements of an array sorted by rank. Consider $z_2$ (the 2nd smallest element) and $z_7$ (the 7th smallest element).

⚡ Interactive Challenge · Staff Level

Algorithmic Design & Complexity Check

Analyze the invariant, asymptotic bound, and hardware behavior before selecting.

How does this correlation affect the derivation of $\mathbb{E}[X]$ vs. the derivation of high-probability tail bounds $\Pr[X > 4n \ln n]$?

Key Takeaways

Two rank-ordered elements z_i and z_j are compared in Quicksort at most once, and if and only if either z_i or z_j is the first pivot selected from Z_{ij} = {z_i, ..., z_j}.
Because all j - i + 1 elements in Z_{ij} are equally likely to be chosen first, Pr[z_i and z_j compared] = 2 / (j - i + 1).
Summing over all pairs via linearity of expectation yields the exact closed form E[X] = 2(n+1)H_n - 4n ≈ 2n ln n ≈ 1.386 n log_2 n comparisons.
Although Randomized Quicksort performs ~38.6% more comparisons than Merge Sort, it performs 3–4x fewer data movements and operates in-place in L1/L2 cache.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue