Algorithm Search⌘K

Proofs: Introsort

These are the human proofs of Introsort, at full length.

Introsort sorts every input, in O(n log n) time full

This proof shows: correctness, termination.

We use three facts proved on other pages. HEAPSORT(A,lo,hi)(A, lo, hi) ends and sorts A[lo…hi−1]A[lo \ldots hi-1], changing nothing else. INSERTION-SORT(A,lo,hi)(A, lo, hi) does the same. HOARE-PARTITION(A,p,r)(A, p, r), for r>pr > p, ends and returns jj with p≤j<rp \le j < r, after permuting A[p…r]A[p \ldots r] so that each item of A[p…j]A[p \ldots j] is at most each item of A[j+1…r]A[j+1 \ldots r]; see Hoare partition. Lines L11 to L15 are that procedure written with << only: "until A[j]≤xA[j] \le x" is "until not x<A[j]x < A[j]".

Lemma 1. For every range with 0≤lo≤hi≤n0 \le lo \le hi \le n and every d≥0d \ge 0, INTRO(A,lo,hi,d)(A, lo, hi, d) ends, changes only A[lo…hi−1]A[lo \ldots hi-1], permutes it, and leaves it sorted.

Proof. By strong induction on hi−lohi - lo. Fix a call, and let [lo0,hi0)[lo_0, hi_0) be its first range. We claim that at the start of each pass of the loop of line L2:

(I) [lo,hi)⊆[lo0,hi0)[lo, hi) \subseteq [lo_0, hi_0); the items of A[lo0…hi0−1]A[lo_0 \ldots hi_0-1] are a permutation of the original ones; A[lo0…lo−1]A[lo_0 \ldots lo-1] and A[hi…hi0−1]A[hi \ldots hi_0-1] are sorted; and each item of A[lo0…lo−1]A[lo_0 \ldots lo-1] is at most each item of A[lo…hi0−1]A[lo \ldots hi_0-1], and each item of A[lo0…hi−1]A[lo_0 \ldots hi-1] is at most each item of A[hi…hi0−1]A[hi \ldots hi_0-1].

At the first pass the two outer parts are empty, so (I) holds. Take a pass with hi−lo>16hi - lo > 16 and d>0d > 0. Lines L5 and L6 only exchange two items of the range. The positions lolo, ⌊(lo+hi)/2⌋\lfloor (lo+hi)/2 \rfloor and hi−1hi - 1 lie in the range, so mm does. Line L7 is called with r=hi−1>lo=pr = hi - 1 > lo = p, so it returns jj with lo≤j<hi−1lo \le j < hi - 1, and each item of A[lo…j]A[lo \ldots j] is at most each item of A[j+1…hi−1]A[j+1 \ldots hi-1]. Both parts [lo,j+1)[lo, j+1) and [j+1,hi)[j+1, hi) are non-empty and shorter than hi−lo≤hi0−lo0hi - lo \le hi_0 - lo_0.

In line L8, the recursive call sorts the left part by the induction hypothesis and touches nothing else. The left part now is sorted, its items are at least the items of A[lo0…lo−1]A[lo_0 \ldots lo-1] (they were in the range before), and at most the items of the right part, which were not moved. So setting lo←j+1lo \leftarrow j + 1 keeps (I). Line L9 is the same with the roles of the parts swapped. Each pass makes hi−lohi - lo smaller, so the loop ends.

The loop ends in one of two ways. If d=0d = 0 at line L3, HEAPSORT sorts A[lo…hi−1]A[lo \ldots hi-1]. If hi−lo≤16hi - lo \le 16, line L10 sorts it. Either way the middle part becomes sorted and keeps its set of items, so by (I) its items still lie between the two outer parts. Then A[lo0…hi0−1]A[lo_0 \ldots hi_0-1] is the sorted left part, then the sorted middle, then the sorted right part, with each part at most the next, which is a sorted sequence. □\square

Theorem (correctness and termination). INTROSORT(A)(A) ends and leaves AA sorted.

Proof. If n≤1n \le 1, the array is sorted and line L1 does nothing. Otherwise line L1 calls INTRO on the whole array, and Lemma 1 applies. □\square

Theorem (time). INTROSORT makes O(nlog⁡n)O(n \log n) comparisons and takes O(nlog⁡n)O(n \log n) time.

Proof. Each call of INTRO receives a budget dd that is at most its caller's budget minus 1, and each pass of the loop lowers dd by 1 before it partitions. Call a partitioning step of depth tt one that runs with the budget 2⌊lg⁡n⌋−t2\lfloor \lg n \rfloor - t after line L4. Then 0≤2⌊lg⁡n⌋−t0 \le 2 \lfloor \lg n \rfloor - t, so t≤2⌊lg⁡n⌋t \le 2 \lfloor \lg n \rfloor. The ranges partitioned at one depth are disjoint, because a step only works inside the range left to it by the step one depth less, and the ranges of the two parts are disjoint. HOARE-PARTITION and the median of three cost O(m)O(m) on mm items, so all the steps of one depth cost O(n)O(n), and all depths cost O(nlog⁡n)O(n \log n). The ranges given to HEAPSORT are disjoint, and with sizes mim_i they cost O(∑milog⁡mi)=O(nlog⁡n)O(\sum m_i \log m_i) = O(n \log n). The ranges given to INSERTION-SORT are disjoint with mi≤16m_i \le 16, so they cost O(∑mi2)=O(16n)O(\sum m_i^2) = O(16 n). ■\blacksquare

Back to Introsort