BACK Mascot image.
← MA0 2 · Introduction to Algorithms and Numerical Analysis

Lesson 5

Trees, Searching and Sorting

Taught

Connectivity and Trees

Connectivity

Definition 5.1 (Connected graph).

An undirected graph is connected if every vertex is reachable from every other vertex. By the proposition on walks and simple paths, equivalently, every pair of distinct vertices is joined by a simple path.

abcdefg
Figure 5.1. A graph with two components, {a,b,c,d}\{a, b, c, d\} and {e,f,g}\{e, f, g\}. The accented edge {e,f}\{e, f\} is a bridge.

The graph of Figure 5.1 is disconnected: no path leads from aa to ee. Its vertices split into {a,b,c,d}\{a, b, c, d\} and {e,f,g}\{e, f, g\}.

Definition 5.2 (Connected component).

A connected component is a maximal connected induced subgraph: no further vertex of the graph can be included while keeping it connected. A graph is connected exactly when it has one component, and an isolated vertex forms a component by itself.

Theorem 5.3 (Edges of a connected graph).

A connected simple undirected graph with n⩾1n \geqslant 1 vertices and mm edges has m⩾n−1m \geqslant n - 1.

Discussion.

The first proof grows the graph from one vertex. At each stage connectivity supplies an edge leaving the part reached so far, and that edge brings in exactly one new vertex, so reaching all nn vertices uses n−1n - 1 distinct edges of the graph.

The second proof is an induction on nn that removes a vertex of smallest degree kk. If k⩾2k \geqslant 2, counting edge-ends gives m⩾nm \geqslant n directly and no induction is needed. If k=1k = 1, removing a vertex of degree one loses exactly one edge, and what is left is still connected, because a vertex of degree one cannot lie in the middle of a path. The hypothesis then applies to the smaller graph. The hypothesis has to be stated for every connected graph on n−1n - 1 vertices, since it is applied to a graph that the proof constructs.

Proof.

Choose one vertex and mark it reached. If fewer than nn vertices are reached, connectivity guarantees an edge from some reached vertex to an unreached one; otherwise no path could lead out of the reached set. Mark that new vertex and keep the connecting edge. Each step reaches exactly one new vertex and keeps an edge not kept before. After n−1n - 1 steps all nn vertices are reached, and we have found n−1n - 1 distinct edges of GG. Therefore m⩾n−1m \geqslant n - 1.

A second proof, by induction, removes a vertex at each step.

Proof.

For each integer n⩾1n \geqslant 1, let P(n)P(n) be the statement: every connected simple undirected graph with nn vertices has at least n−1n - 1 edges.

Base case, P(1)P(1). A simple graph with one vertex has no pair of distinct vertices to join, so it has m=0m = 0 edges. It is connected, and 0=1−10 = 1 - 1.

Step. Fix n⩾2n \geqslant 2 and assume P(n−1)P(n-1). Take an arbitrary connected simple graph G=(S,A)G = (S, A) with ∣S∣=n|S| = n and ∣A∣=m|A| = m, and let k=min⁡v∈Sd(v)k = \min_{v \in S} d(v) be its smallest degree. Because n⩾2n \geqslant 2, a vertex of degree zero would have no path to any other vertex, contradicting connectivity, so k⩾1k \geqslant 1. Either k=1k = 1 or k⩾2k \geqslant 2.

Case k⩾2k \geqslant 2. Every one of the nn vertices has degree at least 22, so ∑v∈Sd(v)⩾2n\sum_{v \in S} d(v) \geqslant 2n. Counting edge-ends gives ∑v∈Sd(v)=2m\sum_{v \in S} d(v) = 2m. Therefore 2m⩾2n2m \geqslant 2n, so m⩾n⩾n−1m \geqslant n \geqslant n - 1. This case needs neither the removal of a vertex nor P(n−1)P(n-1).

Case k=1k = 1. Some vertex vv has exactly one edge, say {v,w}\{v, w\}. Remove vv and that edge, and call the result G′G'. No other edge is removed, because no other edge touches vv, so G′G' has n−1n - 1 vertices and m−1m - 1 edges.

Before applying P(n−1)P(n-1) we check that G′G' is connected. Take distinct vertices a,ba, b of G′G'. Since GG is connected, a simple path joins aa to bb in GG. Neither endpoint is vv, because aa and bb remain. Nor can vv be an internal vertex of the path: the path would have to enter vv along one edge and leave along a second, distinct edge, and vv has only the edge {v,w}\{v, w\}. So the path uses neither vv nor its edge, and it is a path in G′G'. This holds for every pair a,ba, b, so G′G' is connected; when n=2n = 2, G′G' has a single vertex and is connected by definition.

Now G′G' satisfies the hypotheses of P(n−1)P(n-1): it is simple, connected, and has n−1n - 1 vertices. Hence m−1⩾(n−1)−1=n−2m - 1 \geqslant (n - 1) - 1 = n - 2, and adding 11 to both sides gives m⩾n−1m \geqslant n - 1. Both cases prove P(n)P(n) from P(n−1)P(n-1), and with P(1)P(1) induction proves the theorem for every n⩾1n \geqslant 1.

A connected graph with five vertices needs at least four edges, and a path through all five vertices attains the bound. Adding one edge to that path gives five edges and creates a cycle; the theorem does not claim that every connected graph has exactly n−1n - 1 edges.

Definition 5.4 (Articulation vertex, bridge and vertex cut).

An articulation vertex is a vertex whose deletion, together with its edges, increases the number of connected components. A bridge, also called an isthmus, is an edge whose deletion increases that number. A vertex cut of a connected graph is a set of vertices whose deletion disconnects it, provided at least two vertices remain afterwards.

In Figure 5.1, the edge {e,f}\{e, f\} is a bridge: deleting it separates ee from ff and gg. The vertex ff is an articulation vertex: deleting it leaves ee and gg in separate components. All three edges of the left component are bridges too, since that component is a path through four vertices.

Strong Connectivity

For a directed graph, weak connectivity means connectivity once the directions of the arcs are ignored. Requiring directed routes both ways is a stronger condition.

Definition 5.5 (Strong connectivity and the reduced graph).

A directed graph is strongly connected if for every u,vu, v there is a directed walk from uu to vv and one from vv to uu. A strongly connected component is a maximal strongly connected induced subgraph. The reduced graph, or condensation, has one vertex for each strongly connected component, and an arc C→DC \to D whenever C≠DC \neq D and some arc of the original graph goes from a vertex of CC to a vertex of DD.

abcdabcd
Figure 5.2. The left graph is strongly connected. In the right graph no arc returns towards dd or leaves cc, and each vertex is a strongly connected component by itself.

In Figure 5.2, the left graph is strongly connected. In the right one every arc points away from dd and towards cc, so each of its four vertices is a strongly connected component by itself.

abcdefg⟶C1C2
Figure 5.3. A directed graph whose strongly connected components are {a,b,c,d}\{a, b, c, d\} and {e,f,g}\{e, f, g\}, and its reduced graph. The accented arcs are the ones crossing between components.

In Figure 5.3, {a,b,c,d}\{a, b, c, d\} and {e,f,g}\{e, f, g\} are each strongly connected, and the only arcs between them point from the first set to the second. The reduced graph has two vertices C1,C2C_1, C_2 and one arc C1→C2C_1 \to C_2. There cannot be an arc back: that would give reachability both ways and merge the two into one component.

Proposition 5.6 (The reduced graph has no directed cycle).

The reduced graph of any directed graph contains no simple directed cycle.

Discussion.

A cycle through several components would let every vertex in any one of them reach every vertex in the others and come back, so all of them would lie in a single strongly connected component. That contradicts maximality. The proof replaces each arc of the reduced graph by a walk in the original graph.

Proof.

The reduced graph has no loops, since its arcs join distinct components. Suppose C1→C2→⋯→Ck→C1C_1 \to C_2 \to \cdots \to C_k \to C_1 were a simple directed cycle of it with k⩾2k \geqslant 2. Each arc Ci→Ci+1C_i \to C_{i+1} comes from an arc of the original graph from some vertex of CiC_i to some vertex of Ci+1C_{i+1}, and within each component any two vertices are joined by walks both ways. Concatenating, any vertex of C1C_1 can reach any vertex of C2C_2, and any vertex of C2C_2 can reach any vertex of C1C_1 around the rest of the cycle. So C1∪C2C_1 \cup C_2 induces a strongly connected subgraph larger than C1C_1, contradicting the maximality of C1C_1.

Problem 5.1.

Show that a simple undirected graph on nn vertices with more than (n−12)\binom{n-1}{2} edges is connected, and give a disconnected graph with exactly (n−12)\binom{n-1}{2} edges.

Problem 5.2.

Find the strongly connected components and the reduced graph of the graph in Figure 4.8 of Lesson 4.

Trees

Definition 5.7 (Tree and forest).

A tree is a connected acyclic undirected graph. A forest is an undirected graph whose components are trees.

abcdefgabcdefg
Figure 5.4. A tree on seven vertices (left), and the out-arborescence obtained by orienting its edges away from aa (right).

The tree on the left of Figure 5.4 has seven vertices and six edges.

Proposition 5.8 (Counting the edges of a forest).

An acyclic undirected graph with nn vertices, mm edges and cc components satisfies n=m+cn = m + c.

Discussion.

The proof is an induction on the number of edges, and the step removes one. In an acyclic graph every edge is a bridge: if its endpoints were still joined after deleting it, that route together with the edge would be a cycle. So deleting an edge lowers mm by one and raises cc by one, and m+cm + c is unchanged. The base case, a graph with no edges, has every vertex as its own component.

Proof.

For m=0m = 0 every vertex is its own component, so c=nc = n and n=m+cn = m + c. Assume the formula for all acyclic graphs with m−1m - 1 edges, and take one with m>0m > 0 edges. Delete one edge. Its endpoints cannot remain joined by another path, or that path together with the deleted edge would be a cycle. Hence the component containing the edge splits in two and the number of components rises from cc to c+1c + 1. The remaining graph is acyclic with m−1m - 1 edges, so the hypothesis gives n=(m−1)+(c+1)=m+cn = (m - 1) + (c + 1) = m + c.

In particular a forest with nn vertices and cc components has n−cn - c edges.

Theorem 5.9 (Six characterisations of a tree).

Let GG be a finite simple undirected graph with n⩾1n \geqslant 1 vertices. The following are equivalent.

  1. GG is connected and acyclic.
  2. GG is acyclic and has n−1n - 1 edges.
  3. GG is connected and has n−1n - 1 edges.
  4. GG is acyclic, and adding any missing edge creates exactly one simple cycle.
  5. GG is connected, and deleting any edge disconnects it.
  6. Every pair of distinct vertices is joined by exactly one simple path.

In particular each condition characterises a tree. For K1K_1 the clauses about adding or deleting an edge are vacuous.

Discussion.

Six conditions are proved equivalent by a cycle of implications, 1⇒6⇒5⇒4⇒2⇒3⇒11 \Rightarrow 6 \Rightarrow 5 \Rightarrow 4 \Rightarrow 2 \Rightarrow 3 \Rightarrow 1, so that each follows from each. The proof uses two facts. One is the relation between a cycle and two different paths between the same vertices: a cycle gives two routes around it, and two different paths give a cycle where they separate and rejoin. The other is the edge count n=m+cn = m + c for acyclic graphs, which converts between “has n−1n - 1 edges” and “has one component”. The last implication, 3⇒13 \Rightarrow 1, uses the growing argument from the edge bound for connected graphs: it produces n−1n - 1 edges forming a connected acyclic spanning subgraph, and a graph with only n−1n - 1 edges has no others.

Proof.

1⇒61 \Rightarrow 6. Connectivity supplies a simple path between every pair. If two different simple paths joined the same pair, follow them from the common start until they first diverge, and then along the first until it next meets the second; the two segments between those meeting points form a cycle. Thus the path is unique.

6⇒56 \Rightarrow 5. GG is connected by 6. The unique simple path between the endpoints of an edge is that edge itself, so deleting it leaves the endpoints with no path between them.

5⇒45 \Rightarrow 4. If GG had a cycle, deleting one of its edges would leave the endpoints of that edge connected along the rest of the cycle, so it would not disconnect GG. So GG is acyclic. By connectivity a simple path joins the endpoints of any missing edge, and adding the edge closes the path into a simple cycle. Now GG is connected and acyclic, so by 1⇒61 \Rightarrow 6 that path is unique, and every simple cycle through the new edge consists of the new edge and a simple path of GG between its endpoints; so the new simple cycle is unique.

4⇒24 \Rightarrow 2. If GG had two components, adding an edge between them would create no cycle, contrary to 4. Thus c=1c = 1, and since GG is acyclic the edge count n=m+cn = m + c gives m=n−1m = n - 1.

2⇒32 \Rightarrow 3. Since GG is acyclic, the edge count gives c=n−m=n−(n−1)=1c = n - m = n - (n - 1) = 1. One component means that GG is connected.

3⇒13 \Rightarrow 1. Because GG is connected, start at one vertex and repeatedly add an edge to a new vertex until every vertex is reached, as in the first proof of the edge bound. The chosen edges form a spanning subgraph with exactly n−1n - 1 edges, one for each vertex other than the first. It is connected, since every vertex was joined to one reached earlier, and acyclic, since each edge was added to a new vertex and so cannot close a cycle among those already reached. Since GG itself has exactly n−1n - 1 edges, it has no others, so GG is this subgraph and is acyclic.

In the tree of Figure 5.4 the unique path from dd to ff is d,b,c,g,fd, b, c, g, f, of length 44. Removing {b,c}\{b, c\} separates it into two components of sizes 33 and 44. Adding the missing edge {d,g}\{d, g\} creates the unique cycle d,b,c,g,dd, b, c, g, d.

Rooted Trees

Definition 5.10 (Out-arborescence).

An out-arborescence rooted at rr is a directed graph in which there is exactly one directed simple path from rr to each vertex, and whose underlying undirected graph is a tree. Equivalently, rr has in-degree zero, every other vertex has in-degree one, and every vertex is reachable from rr. It has n−1n - 1 arcs.

Orienting the tree of Figure 5.4 away from aa, as a→ba \to b, b→cb \to c, b→db \to d, c→ec \to e, c→gc \to g and g→fg \to f, gives the out-arborescence with root aa on the right of the figure.

Definition 5.11 (Rooted and binary trees).

A rooted tree is a tree with one vertex rr chosen as its root. For a vertex v≠rv \neq r, the vertex before vv on the unique path from rr to vv is the parent of vv, and vv is a child of its parent. A vertex with no children is a leaf, and the others are internal. The depth of vv is d(r,v)d(r, v), the length of the path from rr to vv, and the height of the tree is the largest depth of a vertex. The subtree at vv consists of vv and every vertex whose path from rr passes through vv, rooted at vv.

A binary tree is a rooted tree in which every vertex has at most two children, each labelled as a left child or a right child, with at most one of each. It is full if every internal vertex has exactly two children.

Orienting each edge of a rooted tree from parent to child gives an out-arborescence rooted at rr, since the unique path from rr to vv in the tree becomes the unique directed path. The subtrees at the children of the root of a binary tree are again binary trees, of height one less at most.

Proposition 5.12 (How large a binary tree of given height can be).

A binary tree of height at most hh has at most 2h2^h leaves and at most 2h+1−12^{h+1} - 1 vertices.

Discussion.

A binary tree consists of its root and at most two smaller binary trees below it, so both counts are proved by induction on the height. Each subtree at a child of the root has height at most h−1h - 1, so by induction each has at most 2h−12^{h-1} leaves and 2h−12^h - 1 vertices, and there are at most two of them. The leaves of the whole tree are the leaves of the subtrees, unless the root has no children, and the vertices are those of the subtrees plus the root. The recurrence T(h)=2T(h−1)+1T(h) = 2T(h-1) + 1 with T(0)=1T(0) = 1 is the Tower of Hanoi recurrence shifted by one.

Proof.

We use induction on hh. A binary tree of height 00 is a single vertex, which is a leaf: 1=201 = 2^0 leaves and 1=21−11 = 2^1 - 1 vertices.

Let h⩾1h \geqslant 1 and suppose the claim holds for height at most h−1h - 1. If the root has no children the tree has one leaf and one vertex. Otherwise the root is not a leaf, and every other vertex lies in the subtree at exactly one of the at most two children of the root; each such subtree is a binary tree of height at most h−1h - 1. So there are at most 2⋅2h−1=2h2 \cdot 2^{h-1} = 2^h leaves, and at most 1+2 (2h−1)=2h+1−11 + 2\,(2^h - 1) = 2^{h+1} - 1 vertices.

Corollary 5.13 (Height of a binary tree).

A binary tree with NN vertices has height at least ⌈log⁡2(N+1)⌉−1\lceil \log_2 (N+1) \rceil - 1, and a binary tree with KK leaves has height at least ⌈log⁡2K⌉\lceil \log_2 K \rceil.

Proof.

If the height is hh, the proposition gives N⩽2h+1−1N \leqslant 2^{h+1} - 1, so h+1⩾log⁡2(N+1)h + 1 \geqslant \log_2(N + 1), and since h+1h + 1 is an integer, h+1⩾⌈log⁡2(N+1)⌉h + 1 \geqslant \lceil \log_2(N+1) \rceil. Likewise K⩽2hK \leqslant 2^h gives h⩾log⁡2Kh \geqslant \log_2 K, and so h⩾⌈log⁡2K⌉h \geqslant \lceil \log_2 K \rceil.

Problem 5.3.

Show that every tree with at least two vertices has at least two leaves, that is, vertices of degree one, and that a full binary tree with KK leaves has exactly K−1K - 1 internal vertices.

Problem 5.4.

Count the binary trees with 11, 22, 33 and 44 vertices, where two binary trees are the same only if they have the same shape including the left and right labels. Find a recurrence for the number with NN vertices by considering the sizes of the two subtrees at the root.

Searching

To search an array T[0],…,T[n−1]T[0], \ldots, T[n-1] for a key xx is to return an index ii with key⁡(T[i])=x\operatorname{key}(T[i]) = x, or to report that there is none. This is the find(k) operation of the set interface on an array.

Linear search inspects the entries in order and returns the first index whose key equals xx. The entries need not be in any order.

Linear Search
Input:  an array T[0], …, T[n − 1] and a key x.
Output: an index i with key(T[i]) = x, or −1 if there is none.

    for i ≝ 0 to n − 1 do
        if key(T[i]) = x then return i
    return −1
def linear_search(T, x):
    for i in range(len(T)):
        if T[i] == x:
            return i
    return -1

T = [7, 3, 11, 2, 5]
print(linear_search(T, 2), linear_search(T, 4))   # 3 -1

The invariant before the pass for ii is that xx does not occur among T[0],…,T[i−1]T[0], \ldots, T[i-1]. It holds trivially at i=0i = 0, a pass that does not return extends it by one position, and when the loop ends without returning it says that xx does not occur at all. The variant is n−in - i.

Proposition 5.14 (Cost of linear search).

Linear search on an array of nn entries makes at most nn comparisons with xx, and exactly nn when xx does not occur. If xx occurs exactly once, at each of the nn positions with equal likelihood, the mean number of comparisons is (n+1)/2(n+1)/2. So linear search takes Θ(n)\Theta(n) time in the worst case and on average.

Discussion.

Each pass makes one comparison, and the pass for ii is reached only if xx was not among the first ii entries, so the count is the position at which the search stops, plus one. The worst case is an absent key, which runs through all nn passes. For the mean, a key at position ii costs i+1i + 1 comparisons, and averaging 1,2,…,n1, 2, \ldots, n is the sum of an arithmetic progression divided by nn.

Proof.

The loop makes one comparison per pass and at most nn passes, and it makes all nn when no entry has key xx. If xx occurs exactly once, at position ii, the search stops after the (i+1)(i+1)st comparison. Averaging over the nn equally likely positions and using the sum of an arithmetic progression,

1n∑i=0n−1(i+1)=1n⋅n(n+1)2=n+12.\frac{1}{n}\sum_{i=0}^{n-1} (i + 1) = \frac{1}{n} \cdot \frac{n(n+1)}{2} = \frac{n+1}{2} .

Each comparison and each pass cost a bounded number of elementary operations, so the running time is Θ(n)\Theta(n) in both the worst case and the mean.

On an unsorted array no comparison algorithm does better. We prove this with an adversary, as in the second proof of the proposition on finding a maximum.

Proposition 5.15 (Searching an unsorted array).

Every deterministic algorithm that decides whether xx occurs in an array of nn entries, and learns about the entries only by comparing them with xx, compares xx with all nn entries on some input.

Discussion.

An adversary answers every comparison as though the entry compared were larger than xx, and fixes the entries only as far as those answers require. If the algorithm stops before comparing xx with some entry, that entry is still free. The adversary then sets it equal to xx if the algorithm answered “absent”, and to anything else if it answered “present at that entry”, and in either case the answers given stay true while the output becomes wrong.

Proof.

Answer every comparison of xx with an entry T[j]T[j] as x<key⁡(T[j])x < \operatorname{key}(T[j]). Suppose that on these answers the algorithm stops having compared xx with only some of the entries, and let T[k]T[k] be one it never compared. Every compared entry can be given a key larger than xx, consistently with all the answers. If the algorithm reports that xx does not occur, give T[k]T[k] the key xx: the answers are unchanged, so a deterministic algorithm gives the same wrong report. If it reports an index ii, then either T[i]T[i] was compared and has key larger than xx, or i=ki = k and we give T[k]T[k] a key different from xx; in both cases the report is wrong. So on some input every entry is compared with xx.

Problem 5.5.

Suppose that xx is absent with probability 12\tfrac12, and otherwise lies at each of the nn positions with probability 12n\tfrac{1}{2n}. Find the mean number of comparisons made by linear search.

Problem 5.6.

A sentinel search appends xx at the end of the array before searching, so that the loop needs no test of the index against nn. Write it in Python using append and pop, show that it is correct, and count its comparisons of keys and of indices, compared with the linear search above.

When the array is sorted we can search it by bisection, as in Lesson 3, halving a range of positions instead of an interval of reals.

Let T[0],…,T[n−1]T[0], \ldots, T[n-1] be sorted in nondecreasing key order, meaning key⁡(T[0])⩽key⁡(T[1])⩽⋯⩽key⁡(T[n−1])\operatorname{key}(T[0]) \leqslant \operatorname{key}(T[1]) \leqslant \cdots \leqslant \operatorname{key}(T[n-1]). Binary search for a target key xx keeps a candidate interval of positions [L,R)[L, R), that is L⩽i<RL \leqslant i < R. It compares xx with the key at the middle position m=L+⌊(R−L)/2⌋m = L + \lfloor (R - L)/2 \rfloor. If they are equal it returns mm; if xx is smaller it sets R=mR = m; otherwise it sets L=m+1L = m + 1. It returns −1-1 when the interval is empty, L=RL = R.

Binary Search
Input:  an array T[0], …, T[n − 1] sorted by key, and a key x.
Output: an index m with key(T[m]) = x, or −1 if there is none.

    L ≝ 0;  R ≝ n
    while L < R do
        m ≝ L + ⌊(R − L) / 2⌋
        if x = key(T[m]) then return m
        if x < key(T[m]) then R ≝ m
        else L ≝ m + 1
    return −1
def binary_search(T, x):
    L, R = 0, len(T)
    while L < R:
        m = L + (R - L) // 2
        if x == T[m]:
            return m
        if x < T[m]:
            R = m
        else:
            L = m + 1
    return -1

T = [2, 3, 5, 7, 11, 13, 17]
print(binary_search(T, 11), binary_search(T, 4))   # 4 -1

The invariant is: if xx occurs in TT, then at least one matching index lies in [L,R)[L, R). It holds initially, when the interval is the whole array. Sortedness maintains it: if x<key⁡(T[m])x < \operatorname{key}(T[m]) then every position from mm on has key at least key⁡(T[m])>x\operatorname{key}(T[m]) > x and can be discarded, and symmetrically when x>key⁡(T[m])x > \operatorname{key}(T[m]). At termination either a match has been returned or [L,R)[L, R) is empty, and then by the invariant xx does not occur. The length R−LR - L is a variant: it strictly decreases at every pass. We count one comparison per pass, a three-way comparison of xx with key⁡(T[m])\operatorname{key}(T[m]) whose outcomes are smaller, equal and larger.

Theorem 5.16 (Binary search takes O(log⁡n)O(\log n) comparisons).

For n⩾1n \geqslant 1, binary search on a sorted array of length nn makes at most ⌊log⁡2n⌋+1\lfloor \log_2 n \rfloor + 1 passes of its loop.

Discussion.

The proof is the one for the convergence of bisection, with the length of the interval in place of its width. Because the middle position is removed from the interval, a pass leaves at most ⌊ℓ/2⌋\lfloor \ell/2 \rfloor of the ℓ\ell positions rather than exactly half. After tt passes the length is therefore at most ⌊n/2t⌋\lfloor n/2^t \rfloor, which is zero once 2t>n2^t > n, and the least such tt is ⌊log⁡2n⌋+1\lfloor \log_2 n \rfloor + 1.

Proof.

Let ℓ=R−L⩾1\ell = R - L \geqslant 1 at the start of a pass, so that m−L=⌊ℓ/2⌋m - L = \lfloor \ell/2 \rfloor. If the pass does not return, the new interval is [L,m)[L, m), of length ⌊ℓ/2⌋\lfloor \ell/2 \rfloor, or [m+1,R)[m+1, R), of length ℓ−⌊ℓ/2⌋−1=⌈ℓ/2⌉−1⩽⌊ℓ/2⌋\ell - \lfloor \ell/2 \rfloor - 1 = \lceil \ell/2 \rceil - 1 \leqslant \lfloor \ell/2 \rfloor. So after each pass the length is at most ⌊ℓ/2⌋\lfloor \ell / 2 \rfloor, and since ⌊⌊a⌋/2⌋=⌊a/2⌋\bigl\lfloor \lfloor a \rfloor / 2 \bigr\rfloor = \lfloor a/2 \rfloor for a⩾0a \geqslant 0, after tt passes it is at most ⌊n/2t⌋\lfloor n / 2^t \rfloor. For t=⌊log⁡2n⌋+1t = \lfloor \log_2 n \rfloor + 1 we have 2t>n2^t > n, so the length is 00 and the loop has stopped.

The bound is attained, for example by an unsuccessful search for a key smaller than every key in the array: it moves left at every pass, and the length goes n,⌊n/2⌋,⌊n/4⌋,…n, \lfloor n/2 \rfloor, \lfloor n/4 \rfloor, \ldots down to 00.

3150246<>
Figure 5.5. The indices binary search tests on an array of length 77. A search starts at the root and moves left when the target is smaller than the tested key and right when it is larger; it may stop at any vertex on finding the target.

For n=7=23−1n = 7 = 2^3 - 1 the positions tested form the binary tree of Figure 5.5: the root is the first position tested, and the left and right children of a position are the ones tested next when the target is smaller or larger. A search that stops at depth tt has made t+1t + 1 comparisons.

Proposition 5.17 (Mean cost of a successful search).

Let n=2k−1n = 2^k - 1 and suppose the target is one of the nn keys, each equally likely, with all keys distinct. The mean number of comparisons made by binary search is

12k−1∑i=1ki 2i−1=k−1+k2k−1.\frac{1}{2^k - 1} \sum_{i=1}^{k} i\, 2^{i-1} = k - 1 + \frac{k}{2^k - 1} .

Discussion.

For n=2k−1n = 2^k - 1 every interval that arises has odd length and splits into two equal halves, so the tree of tested positions is full, with 2i−12^{i-1} positions at depth i−1i - 1 for i=1,…,ki = 1, \ldots, k, and a target at depth i−1i - 1 is found after ii comparisons. That gives the sum. To evaluate Sk=∑i=1ki 2i−1S_k = \sum_{i=1}^{k} i\,2^{i-1} we proceed as for the geometric sum and compare SkS_k with 2Sk−12S_{k-1}, whose terms are those of SkS_k shifted by one place with the multiplier lowered by one, so that the difference is a geometric sum.

Proof.

An interval of length 2j−12^j - 1 with j⩾2j \geqslant 2 has middle position with 2j−1−12^{j-1} - 1 positions on each side, so by induction on jj the positions tested form a full binary tree with 2i−12^{i-1} positions at depth i−1i - 1, for 1⩽i⩽k1 \leqslant i \leqslant k. The target at a position of depth i−1i - 1 is found at the iith comparison, so the total over the nn equally likely targets is Sk=∑i=1ki 2i−1S_k = \sum_{i=1}^{k} i\,2^{i-1}.

For k⩾2k \geqslant 2,

Sk−2Sk−1=∑i=1ki 2i−1−∑i=2k(i−1) 2i−1=1+∑i=2k2i−1=2k−1S_k - 2S_{k-1} = \sum_{i=1}^{k} i\,2^{i-1} - \sum_{i=2}^{k} (i-1)\,2^{i-1} = 1 + \sum_{i=2}^{k} 2^{i-1} = 2^k - 1

by the geometric sum. So Sk=2Sk−1+2k−1S_k = 2S_{k-1} + 2^k - 1 with S1=1S_1 = 1, and induction gives Sk=(k−1)2k+1S_k = (k-1)2^k + 1: at k=1k = 1 both sides are 11, and 2((k−2)2k−1+1)+2k−1=(k−1)2k+12\bigl((k-2)2^{k-1} + 1\bigr) + 2^k - 1 = (k-1)2^k + 1. Finally (k−1)2k+1=(k−1)(2k−1)+k(k-1)2^k + 1 = (k-1)(2^k - 1) + k, and dividing by 2k−12^k - 1 gives the stated mean.

The mean is Θ(log⁡n)\Theta(\log n). For arbitrary nn interval halving gives an O(log⁡n)O(\log n) worst case, by the theorem. For a lower bound on the mean, the positions found within tt comparisons are those of depth less than tt in the tree of tested positions, and by the proposition on binary trees there are at most 2t−12^t - 1 of them. Take t=⌊log⁡2n⌋−1t = \lfloor \log_2 n \rfloor - 1 for n⩾4n \geqslant 4; then 2t−1<n/22^t - 1 < n/2, so fewer than half the nn positions are found within tt comparisons. At least half need more than tt, and the mean over equally likely targets is Ω(log⁡n)\Omega(\log n).

A Lower Bound for Searching

A deterministic comparison search algorithm can be pictured as a fixed binary decision tree of all its possible executions, in which each internal vertex is a comparison the algorithm makes. The algorithm walks down the tree from the root: the first comparison it makes is at the root, and according to the outcome it continues at one of the two children. It stops on reaching a leaf, which records its output, so there must be a leaf for every possible output. The number of comparisons on a given input is the depth of the leaf reached, and the worst-case number is the height of the tree.

Theorem 5.18 (Searching needs Ω(log⁡n)\Omega(\log n) comparisons).

Every deterministic algorithm that searches a set of nn items with distinct keys for a given key, using only comparisons of keys, makes at least ⌈log⁡2(n+1)⌉\lceil \log_2(n+1) \rceil comparisons on some input.

Discussion.

The proof counts outputs. A search can end in n+1n + 1 different ways, one for each stored item and one for “not present”, and each must appear at some leaf of the decision tree. A binary tree with that many leaves has height at least log⁡2(n+1)\log_2(n+1) by the corollary on heights, and height is the worst-case number of comparisons.

Proof.

The algorithm has n+1n + 1 possible outputs, the nn stored items and the report that no item has the key, and each occurs for some input, so its decision tree has at least n+1n + 1 leaves. By the corollary on heights of binary trees its height is at least ⌈log⁡2(n+1)⌉\lceil \log_2(n+1) \rceil, and some input follows a path of that length.

So binary search makes the least possible number of comparisons, up to a constant factor. The same argument with any fixed number of outcomes per comparison in place of two still gives Ω(log⁡n)\Omega(\log n). Hashing does better because it is not a comparison algorithm: reading a direct access array at an index computed from the key can go to any one of its slots in a single step.

Problem 5.7.

Modify binary_search so that it returns the least index ii with key⁡(T[i])⩾x\operatorname{key}(T[i]) \geqslant x, or nn if there is none, still in O(log⁡n)O(\log n) time. State the invariant, and use your function to count the entries of a sorted array lying in an interval [a,b)[a, b).

Problem 5.8.

A programmer writes L = m in place of L = m + 1. Give an array and a key for which the modified loop never terminates, and say which part of the termination argument fails.

Sorting

The Sorting Problem

We sort records, each carrying a key that can be compared with other keys. A comparison sort learns about the keys only by comparing them, as in the comparison model.

Definition 5.19 (Sorting, in place and stable).

A sort returns the same records as its input, rearranged in nondecreasing key order. A sort is in place if it uses O(1)O(1) auxiliary cells besides the input array, apart from stack space for recursion if that is being counted separately. A sort is stable if records with equal keys keep their input order.

Checking only that the output keys are in order is not enough to certify a sort: the output must also be a rearrangement of the input records, with none lost or duplicated. Stability can be forced in any comparison sort by comparing the pairs (key,original index)(\text{key}, \text{original index}) lexicographically, first by key and then by index, though storing the original indices may change the space requirements.

Example 5.20 (Stable and unstable).

Sorting (2,a),(1,b),(2,c)(2, a), (1, b), (2, c) stably by the numeric key gives (1,b),(2,a),(2,c)(1, b), (2, a), (2, c): the two records with key 22 remain in their original relative order. A routine that returns (1,b),(2,c),(2,a)(1, b), (2, c), (2, a) has sorted the records, but not stably.

A rearrangement of nn records is a permutation, written as the list π1,…,πn\pi_1, \ldots, \pi_n of the input positions in output order, and there are n!n! of them. A brute-force method tests permutations one by one until it finds an arrangement in increasing order of pairwise distinct keys. A candidate may need n−1n - 1 comparisons of adjacent keys to certify that it is sorted, and there are n!n! candidates.

Definition 5.21 (Inversion).

An inversion of an array T[0],…,T[n−1]T[0], \ldots, T[n-1] is a pair of positions i<ji < j with key⁡(T[i])>key⁡(T[j])\operatorname{key}(T[i]) > \operatorname{key}(T[j]).

A sorted array has no inversions, and an array of distinct keys in decreasing order has all (n2)\binom{n}{2} possible ones, one for each unordered pair of positions. An exchange of two adjacent records T[j],T[j+1]T[j], T[j+1] with key⁡(T[j])>key⁡(T[j+1])\operatorname{key}(T[j]) > \operatorname{key}(T[j+1]) removes exactly one inversion, that pair itself, since the relative order of every other pair is unchanged.

Selection Sort

Selection sort repeatedly removes the largest remaining key and puts it at the next final position from right to left. Having already sorted the largest items into the subarray A[i+1:], it scans A[:i+1] for the largest item not yet placed and swaps it with A[i].

Selection Sort
Input:  an array A[0], …, A[n − 1].
Output: the same array, sorted.

    for i ≝ n − 1 down to 1 do
        m ≝ i
        for j ≝ 0 to i − 1 do
            if key(A[m]) < key(A[j]) then m ≝ j
        swap A[m] and A[i]
def selection_sort(A):
    for i in range(len(A) - 1, 0, -1):   # O(n) passes
        m = i                            # O(1) index of the largest so far
        for j in range(i):               # O(i) search A[:i] for a larger item
            if A[m] < A[j]:              # O(1)
                m = j                    # O(1) new largest found
        A[m], A[i] = A[i], A[m]          # O(1) swap

A = [5, 2, 9, 1, 5, 6]
selection_sort(A)
print(A)   # [1, 2, 5, 5, 6, 9]

The invariant before the pass for ii is that A[i+1:] holds the n−1−in - 1 - i largest records in sorted order, and that every key in A[:i+1] is at most every key in A[i+1:]. The pass moves the largest key of A[:i+1] to position ii, which extends the sorted suffix by one. When the loop ends, at i=0i = 0, the suffix A[1:] is sorted and A[0] holds the smallest key.

Finding the maximum among kk remaining keys takes k−1k - 1 comparisons, which is the least possible by the proposition on finding a maximum, so the method uses

∑k=1n(k−1)=n(n−1)2=Θ(n2)\sum_{k=1}^{n} (k - 1) = \frac{n(n-1)}{2} = \Theta(n^2)

comparisons, even on an input that is already sorted. It performs at most n−1n - 1 swaps. It is in place: apart from the array it uses the names i, j and m.

Example 5.22 (Selection on (1,2,4)(1, 2, 4)).

On (1,2,4)(1, 2, 4), selection chooses 44, then 22, then 11. Each chosen maximum occupies its final, rightmost open position, and the comparison counts are 2+1+0=32 + 1 + 0 = 3.

Selection sort, swapping as above, is not stable. On the records (2,a),(1,b),(1,c)(2, a), (1, b), (1, c), compared by key alone, the first pass finds (2,a)(2, a) at position 00 as the maximum and swaps it with (1,c)(1, c) at position 22, giving (1,c),(1,b),(2,a)(1, c), (1, b), (2, a); the second pass makes no change, and (1,c)(1, c) ends before (1,b)(1, b).

Bubble Sort

A left-to-right pass of bubble sort compares T[j]T[j] with T[j+1]T[j+1] and exchanges them if they are out of order. The largest key in the scanned part moves to its right end, so a full pass places the maximum of the active prefix at its final position. Repeating on successively shorter prefixes sorts the array. If a pass makes no exchange, the array is already sorted and the algorithm can stop early.

Bubble Sort
Input:  an array T[0], …, T[n − 1].
Output: the same array, sorted.

    for last ≝ n − 1 down to 1 do
        changed ≝ false
        for j ≝ 0 to last − 1 do
            if key(T[j]) > key(T[j + 1]) then
                swap T[j] and T[j + 1];  changed ≝ true
        if not changed then stop
def bubble_sort(T):
    for last in range(len(T) - 1, 0, -1):
        changed = False
        for j in range(last):
            if T[j] > T[j + 1]:
                T[j], T[j + 1] = T[j + 1], T[j]
                changed = True
        if not changed:
            return

The invariant before the pass with a given last is that T[last+1:] holds the largest records in sorted order, each at least every key in T[:last+1].

For n⩾1n \geqslant 1, the worst-case number of comparisons is n(n−1)/2n(n-1)/2, and the best case, on a sorted input, is n−1n - 1 with the early stop. Over the n!n! orders of nn distinct keys, counted equally, the mean number of comparisons is still Θ(n2)\Theta(n^2). For the lower bound, the smallest key begins in the last half of the array, at one of the ⌈n/2⌉\lceil n/2 \rceil positions from ⌊n/2⌋\lfloor n/2 \rfloor on, in at least half of the n!n! orders. A left-to-right pass moves the smallest key left by at most one position, so on those orders at least ⌊n/2⌋\lfloor n/2 \rfloor passes are needed, and each of those passes makes at least n/2n/2 comparisons. Thus at least half the orders cost at least ⌊n/2⌋⋅n/2\lfloor n/2 \rfloor \cdot n/2 comparisons, and the mean is Ω(n2)\Omega(n^2). The upper bound O(n2)O(n^2) holds on every input.

Proposition 5.23 (Exchanges in bubble sort).

The number of exchanges bubble sort makes on an array equals its number of inversions. Over the n!n! orders of nn distinct keys, counted equally, the mean number of inversions is n(n−1)/4n(n-1)/4.

Discussion.

For the first statement, every exchange removes exactly one inversion, and the algorithm stops with a sorted array, which has none; so the number of exchanges is the number of inversions at the start. For the mean, pair each order with its reverse. A pair of positions is inverted in exactly one of the two, so the inversion counts of an order and of its reverse add up to (n2)\binom{n}{2}. The reversal pairs the n!n! orders off, so the average over all of them is half of (n2)\binom{n}{2}.

Proof.

Bubble sort exchanges T[j]T[j] and T[j+1]T[j+1] only when key⁡(T[j])>key⁡(T[j+1])\operatorname{key}(T[j]) > \operatorname{key}(T[j+1]), and such an exchange of adjacent records removes exactly one inversion. It stops with a sorted array, which has no inversions. So if the input has II inversions, it makes exactly II exchanges.

For an order π\pi of distinct keys let inv⁡(π)\operatorname{inv}(\pi) be its number of inversions, and let πˉ\bar\pi be the reverse order. For each of the (n2)\binom{n}{2} pairs of keys, exactly one of π\pi and πˉ\bar\pi lists the larger key first, so inv⁡(π)+inv⁡(πˉ)=(n2)\operatorname{inv}(\pi) + \operatorname{inv}(\bar\pi) = \binom{n}{2}. Reversal is a bijection from the set of orders to itself, so summing over all orders,

2∑πinv⁡(π)=∑π(inv⁡(π)+inv⁡(πˉ))=n!(n2),2\sum_{\pi} \operatorname{inv}(\pi) = \sum_{\pi} \bigl(\operatorname{inv}(\pi) + \operatorname{inv}(\bar\pi)\bigr) = n!\binom{n}{2},

and the mean is 12(n2)=n(n−1)/4\tfrac12\binom{n}{2} = n(n-1)/4.

Exchanges happen only on strict inversions, so records with equal keys are never exchanged with each other and keep their original order: bubble sort is stable. Small keys near the right end move slowly, one position per pass, and are sometimes called turtles; large keys near the left move quickly to the right and are called hares. Bidirectional bubble sort alternates the direction of its passes to move turtles faster, and gnome sort steps back to recheck the previous adjacent pair after each exchange.

Insertion Sort

Insertion sort treats T[0..i-1] as sorted and inserts T[i] into that prefix by shifting the larger records one position right.

Insertion Sort
Input:  an array T[0], …, T[n − 1].
Output: the same array, sorted.

    for i ≝ 1 to n − 1 do
        x ≝ T[i];  j ≝ i − 1
        while j ⩾ 0 and key(x) < key(T[j]) do
            T[j + 1] ≝ T[j];  j ≝ j − 1
        T[j + 1] ≝ x
def insertion_sort(T):
    for i in range(1, len(T)):           # O(n) passes
        x = T[i]                         # the record to insert
        j = i - 1
        while j >= 0 and x < T[j]:       # O(i) shift larger records right
            T[j + 1] = T[j]
            j = j - 1
        T[j + 1] = x                     # fill the gap

The invariant before the pass for ii is that T[0..i-1] holds the first ii input records in sorted order. The final assignment T[j+1] = x places the saved record in the gap left by the shifts, and it is essential: writing xx anywhere else would duplicate one record and lose another. The while test relies on and stopping as soon as j >= 0 is False, so that T[-1] is never read.

Example 5.24 (Insertion on (4,2,3)(4, 2, 3)).

On (4,2,3)(4, 2, 3), save 22, shift 44 right and write 22 at index 00, giving (2,4,3)(2, 4, 3). Next save 33, shift 44 right and write 33 at index 11, giving (2,3,4)(2, 3, 4).

Insertion sort is Θ(n2)\Theta(n^2) in the worst case, on an input in decreasing order, where the pass for ii shifts all ii records of the prefix, and Θ(n)\Theta(n) on an already sorted input, where every pass makes one comparison and no shift.

In Place and Stable

Selection sort, bubble sort and insertion sort are all in place: each uses a constant amount of space besides the array, and acts on the array only by comparisons and by exchanging or moving records. Bubble sort and insertion sort are stable, insertion sort because a record is never shifted past one with an equal key. Selection sort, as implemented above, is not.

Problem 5.9.

Show that insertion sort performs exactly as many shifts T[j + 1] = T[j] as the input has inversions, and deduce the mean number of shifts over the n!n! orders of nn distinct keys. Give an input of length nn on which selection sort makes n−1n - 1 swaps and insertion sort makes no shift.

Problem 5.10.

Run bubble sort and insertion sort on (3,1,4,1,5,9,2,6)(3, 1, 4, 1, 5, 9, 2, 6), keeping the two 11s apart as 1a1_a and 1b1_b. Record the array after each pass of each algorithm, count the comparisons and exchanges or shifts, and confirm that both outputs keep 1a1_a before 1b1_b.

Merge Sort

Merge sort splits the array in half, sorts each half recursively, and merges the two sorted halves into one.

Proposition 5.25 (Merging two sorted arrays).

Two sorted arrays of lengths a,b>0a, b > 0 can be merged into one sorted array with at most a+b−1a + b - 1 key comparisons. If ties take the record from the left array first, the merge is stable.

Discussion.

The smallest remaining record of the whole is always at the front of one of the two arrays, because each array is sorted, so one comparison of the two front records decides which comes next. Each comparison outputs one record, and once one array is exhausted the rest of the other is copied without comparing. At least one record, the last, is output without a comparison, which gives a+b−1a + b - 1. For stability, when the two front keys are equal, taking the left one keeps records from the left array ahead of equal records from the right.

Proof.

Keep a cursor at the first unmerged element of each array. While both arrays have elements left, compare their current heads and copy the smaller one to the output. Its key is no larger than any remaining key, because each input array is already sorted. So the output stays sorted, and no record is lost or copied twice. Each comparison advances one cursor, so after at most a+b−1a + b - 1 comparisons one array must be empty; the other array’s sorted tail is then appended without further comparisons. On equal keys, choosing the left head first preserves the original order between the two arrays, while the order within each array is unchanged.

Merge
Input:  sorted arrays X and Y.
Output: a sorted array Z holding the records of X and Y.

    i ≝ 0;  j ≝ 0;  Z ≝ empty
    while i < length(X) and j < length(Y) do
        if key(X[i]) ⩽ key(Y[j]) then append X[i] to Z;  i ≝ i + 1
        else append Y[j] to Z;  j ≝ j + 1
    append the remaining part of X, then of Y, to Z
    return Z

Merge Sort
Input:  an array A.
Output: a sorted array holding the records of A.

    if length(A) ⩽ 1 then return A
    h ≝ ⌊length(A) / 2⌋
    return Merge(Merge Sort(A[0..h − 1]), Merge Sort(A[h..length(A) − 1]))
def merge(X, Y):
    i, j, Z = 0, 0, []
    while i < len(X) and j < len(Y):
        if X[i] <= Y[j]:
            Z.append(X[i])
            i = i + 1
        else:
            Z.append(Y[j])
            j = j + 1
    return Z + X[i:] + Y[j:]

def merge_sort(A):
    if len(A) <= 1:
        return A
    h = len(A) // 2
    return merge(merge_sort(A[:h]), merge_sort(A[h:]))

print(merge_sort([15, 5, 64, 8, 12, 6, 4, 35]))   # [4, 5, 6, 8, 12, 15, 35, 64]

Theorem 5.26 (Merge sort).

On an array of nn records with comparable keys, merge sort returns a sorted rearrangement of the input, and choosing the left head on equal keys makes it stable. It uses O(nlog⁡n)O(n \log n) time and O(n)O(n) auxiliary storage. For n=2kn = 2^k, its worst-case number of key comparisons is exactly nlog⁡2n−n+1n \log_2 n - n + 1.

Discussion.

Correctness is an induction on nn whose step is the merge proposition: the recursive calls are on shorter arrays, so by the hypothesis they return sorted rearrangements of the halves, and merging those gives a sorted rearrangement of the whole. For the time, we count level by level: the arrays at one level are disjoint pieces of the input, so merging all of them costs O(n)O(n), and halving gives about log⁡2n\log_2 n levels. For the exact count, the merge proposition allows a merge of two halves of size 2k−12^{k-1} up to 2k−12^k - 1 comparisons, which gives the recurrence Ck=2Ck−1+2k−1C_k = 2C_{k-1} + 2^k - 1. It remains to find an input that attains the bound at every merge, and to solve the recurrence.

Proof.

We prove correctness by induction on nn. For n⩽1n \leqslant 1 the array is already sorted. If n>1n > 1, both halves have fewer than nn elements, so the recursive calls terminate and, by induction, return sorted rearrangements of their halves. The merge proposition combines those into a sorted rearrangement of the whole input. Its tie rule preserves the order of equal-key records across the halves, so induction also proves stability. The recursion terminates because every call on more than one record is made on strictly shorter arrays.

At any fixed level of the recursion the subarrays are disjoint parts of the original array, so their lengths add up to at most nn. Merging each costs time proportional to its length, so the total work per level is O(n)O(n). Halving gives at most ⌈log⁡2n⌉\lceil \log_2 n \rceil levels of merging, and therefore O(nlog⁡n)O(n \log n) time. At any instant the arrays alive along the current chain of calls have lengths at most n,n/2,n/4,…n, n/2, n/4, \ldots together with their merge outputs, so at most O(n)O(n) auxiliary storage is present at once; the recursion stack has O(log⁡n)O(\log n) frames.

When n=2kn = 2^k, a merge of two sorted halves of equal size can need all 2k−12^k - 1 comparisons the merge proposition allows: give the left half the records of odd rank and the right half those of even rank, so that their sorted values alternate until only one remains. Apply the same odd–even assignment recursively within each half. Arranging the input by these assignments makes every merge attain its worst case. Writing CkC_k for the worst case at size 2k2^k, this gives C0=0C_0 = 0 and

Ck=2Ck−1+2k−1.C_k = 2C_{k-1} + 2^k - 1 .

We claim Ck=k2k−2k+1C_k = k2^k - 2^k + 1. At k=0k = 0 both sides are 00. Assuming the formula at k−1k - 1,

Ck=2((k−1)2k−1−2k−1+1)+2k−1=k2k−2k+1.C_k = 2\bigl((k-1)2^{k-1} - 2^{k-1} + 1\bigr) + 2^k - 1 = k2^k - 2^k + 1 .

With n=2kn = 2^k this is nlog⁡2n−n+1n \log_2 n - n + 1. It is a count of key comparisons, and an array of one record makes none.

Problem 5.11.

Write an iterative merge sort that merges adjacent runs of length 11, then 22, then 44, and so on, with no recursion. Prove it correct with a loop invariant on the run length, and show that it makes at most n⌈log⁡2n⌉n \lceil \log_2 n \rceil comparisons.

Quicksort

Quicksort chooses a pivot, partitions the array so that smaller keys precede the pivot and larger or equal keys follow it, and then recursively sorts the two sides. For the partition in place, with the pivot initially at position LL, the invariant before inspecting position kk is: positions L+1,…,pL+1, \ldots, p hold keys smaller than the pivot, positions p+1,…,k−1p+1, \ldots, k-1 hold keys larger than or equal to it, and positions k,…,R−1k, \ldots, R-1 are unexamined. A newly found smaller record is swapped into position p+1p + 1 and pp is increased. At the end the pivot is swapped with the record at position pp. Partitioning ss records compares each of the other s−1s - 1 records with the pivot once.

The pseudocode below sorts the half-open slice [L,R)[L, R) of the original array in place; no slice is copied. The partition moves only records strictly smaller than the pivot to its left.

Quicksort
Input:  an array A and positions L ⩽ R.
Output: A with A[L..R − 1] sorted.

    Sort(A, L, R):
        if R − L ⩽ 1 then return
        choose q uniformly from L, …, R − 1;  swap A[L] and A[q]
        pivot ≝ A[L];  p ≝ L
        for k ≝ L + 1 to R − 1 do
            if key(A[k]) < key(pivot) then
                p ≝ p + 1;  swap A[p] and A[k]
        swap A[L] and A[p]
        Sort(A, L, p);  Sort(A, p + 1, R)

The statement from random import randrange makes randrange available: randrange(L, R) returns an integer chosen uniformly from L,…,R−1L, \ldots, R - 1, the same range as range(L, R).

from random import randrange

def sort_slice(A, L, R):
    if R - L <= 1:
        return
    q = randrange(L, R)
    A[L], A[q] = A[q], A[L]
    pivot = A[L]
    p = L
    for k in range(L + 1, R):
        if A[k] < pivot:
            p = p + 1
            A[p], A[k] = A[k], A[p]
    A[L], A[p] = A[p], A[L]
    sort_slice(A, L, p)
    sort_slice(A, p + 1, R)

def quicksort(A):
    sort_slice(A, 0, len(A))

If the pivot has rank rr among nn distinct keys, meaning r−1r - 1 keys are smaller, the number of comparisons satisfies

C(n)=n−1+C(r−1)+C(n−r),C(0)=C(1)=0.C(n) = n - 1 + C(r - 1) + C(n - r), \qquad C(0) = C(1) = 0 .

Example 5.27 (One partition).

For the input (15,5,64,8,12,6,4,35)(15, 5, 64, 8, 12, 6, 4, 35), take the first value 1515 as pivot, with no random swap. The partition puts the five smaller values 5,8,12,6,45, 8, 12, 6, 4 before it and 64,3564, 35 after it, and the in-place result is (4,5,8,12,6,15,64,35)(4, 5, 8, 12, 6, 15, 64, 35). Recursively sorting the two sides gives (4,5,6,8,12,15,35,64)(4, 5, 6, 8, 12, 15, 35, 64). The order within each side before the recursion depends on the partition routine.

Proposition 5.28 (Worst and best case of quicksort).

For nn distinct keys, any run of quicksort makes at most (n2)=n(n−1)/2\binom{n}{2} = n(n-1)/2 key comparisons. This bound is attained when every pivot is the smallest or largest key in its current subarray, so the worst-case number of comparisons is Θ(n2)\Theta(n^2). If every pivot splits its subarray as evenly as possible, the number of comparisons is Θ(nlog⁡n)\Theta(n \log n).

Discussion.

For the upper bound we count pairs of keys. Two keys are compared only when one of them is the pivot, and a pivot is left out of all later calls, so no pair is compared twice; there are (n2)\binom{n}{2} pairs. When every pivot is extreme, one side of each partition is empty, and the recurrence becomes Wn=(n−1)+Wn−1W_n = (n-1) + W_{n-1}, which sums to the same number. For even splits, the depth of the recursion is logarithmic and each level costs at most nn comparisons, which gives O(nlog⁡n)O(n \log n); the matching lower bound needs a count of how many levels still have large subarrays, and how many comparisons each such level makes.

Proof.

Partitioning a subarray of size ss compares its pivot with each of the other s−1s - 1 keys exactly once. A given pair of keys can be compared only when one of them is the pivot, and that pivot is then excluded from all recursive subarrays. Thus no pair is compared twice, and there are at most (n2)\binom{n}{2} comparisons. If every pivot is extreme, one recursive side is empty and the other has size n−1n - 1. The count obeys Wn=(n−1)+Wn−1W_n = (n-1) + W_{n-1} with W0=W1=0W_0 = W_1 = 0, and hence

Wn=(n−1)+(n−2)+⋯+1=n(n−1)2.W_n = (n-1) + (n-2) + \cdots + 1 = \frac{n(n-1)}{2} .

For balanced splits, the recursion has O(log⁡n)O(\log n) levels. At any level the active subarrays are disjoint, so their sizes add up to at most nn and their partitions make at most nn comparisons in total. This proves O(nlog⁡n)O(n \log n).

For the reverse bound, number the levels from ℓ=0\ell = 0. A balanced split of ss records leaves at least ⌊(s−1)/2⌋\lfloor (s-1)/2 \rfloor records on either side. Applying this at each level shows that every subarray at level ℓ\ell has at least ⌊(n+1)/2ℓ⌋−1\lfloor (n+1)/2^\ell \rfloor - 1 records. For 0⩽ℓ⩽⌊log⁡2n⌋−20 \leqslant \ell \leqslant \lfloor \log_2 n \rfloor - 2 this is at least 33, so no branch has yet ended at a call on one record. Before such a level at most 2ℓ−12^\ell - 1 pivots have been removed, and the remaining records all belong to at most 2ℓ2^\ell active subarrays. A subarray of size ss uses s−1s - 1 comparisons, so level ℓ\ell uses at least n−(2ℓ−1)−2ℓ=n−2ℓ+1+1⩾n/2n - (2^\ell - 1) - 2^\ell = n - 2^{\ell+1} + 1 \geqslant n/2 comparisons. There are Θ(log⁡n)\Theta(\log n) such levels for large nn, which proves Ω(nlog⁡n)\Omega(n \log n).

The in-place partition is not stable in general. The recursive calls use O(log⁡n)O(\log n) stack space for balanced splits and O(n)O(n) when the splits are as uneven as possible.

With a random pivot the cost is an average over the random choices, in the sense of the expectation defined for hashing in the last lesson.

Remark (Expected cost of a randomized algorithm).

The expectation used for hashing is an average over one uniform choice. Randomized quicksort makes a uniform choice at every call, and its expected cost is defined in the same way one call at a time: if a call on ss keys chooses the pivot rank rr with probability 1/s1/s, and the rest of the run then has expected cost crc_r, the expected cost of the call is its own comparisons plus 1s∑rcr\tfrac{1}{s}\sum_{r} c_r. Equivalently, it is the average over all complete runs, each run weighted by the product of the probabilities 1/s1/s of the choices it makes.

Theorem 5.29 (Expected cost of randomized quicksort).

Fix any input order of nn distinct keys. At every recursive call choose the pivot uniformly from the current subarray, and compare it once with each other key there. Let EnE_n be the expected total number of key comparisons. Then

E0=0,En=2(n+1)Hn−4n(n⩾1).E_0 = 0, \qquad E_n = 2(n+1)H_n - 4n \quad (n \geqslant 1) .

In particular En=Θ(nlog⁡n)E_n = \Theta(n \log n). The expectation is over the pivot choices; the input order is fixed and need not be random.

Discussion.

The proof has four steps. Conditioning on the rank of the first pivot turns the expectation into a recurrence in which EnE_n depends on all of E0,…,En−1E_0, \ldots, E_{n-1} through their sum. Writing the recurrence at nn and at n−1n - 1 and subtracting removes the sum, leaving a first-order recurrence with variable coefficients, of the kind the summation factor of the last lesson solves; the example there is the same recurrence with 2n2n in place of 2(n−1)2(n-1). Here the answer is checked by induction instead. Finally the bounds on HnH_n give the growth rate.

Proof.

Step 1: condition on the first pivot. A subarray of size at most 11 needs no comparisons, so E0=E1=0E_0 = E_1 = 0. For n⩾2n \geqslant 2 the first partition costs exactly n−1n - 1 comparisons. The pivot is equally likely to have any rank r∈{1,…,n}r \in \{1, \ldots, n\} among the keys. Rank rr leaves r−1r - 1 smaller keys on the left and n−rn - r larger keys on the right. Because every later pivot is chosen uniformly within its own subarray, the expected costs of those two calls are Er−1E_{r-1} and En−rE_{n-r}, whatever their internal order. Averaging over the nn possible ranks gives

En=1n∑r=1n((n−1)+Er−1+En−r)=n−1+2n∑j=0n−1Ej,E_n = \frac{1}{n}\sum_{r=1}^{n} \bigl((n-1) + E_{r-1} + E_{n-r}\bigr) = n - 1 + \frac{2}{n}\sum_{j=0}^{n-1} E_j ,

since both r−1r - 1 and n−rn - r run once through 0,…,n−10, \ldots, n-1 as rr runs through 1,…,n1, \ldots, n.

Step 2: remove the sum. Multiply this equation by nn, and write the same equation for n−1n - 1 multiplied by n−1n - 1:

nEn=n(n−1)+2∑j=0n−1Ej,(n−1)En−1=(n−1)(n−2)+2∑j=0n−2Ej.\begin{aligned} nE_n &= n(n-1) + 2\sum_{j=0}^{n-1} E_j, \\ (n-1)E_{n-1} &= (n-1)(n-2) + 2\sum_{j=0}^{n-2} E_j . \end{aligned}

Subtract the second from the first. The two sums differ only by En−1E_{n-1}, while n(n−1)−(n−1)(n−2)=2(n−1)n(n-1) - (n-1)(n-2) = 2(n-1). Therefore nEn−(n−1)En−1=2(n−1)+2En−1nE_n - (n-1)E_{n-1} = 2(n-1) + 2E_{n-1}, that is,

nEn=(n+1)En−1+2(n−1).nE_n = (n+1)E_{n-1} + 2(n-1) .

Step 3: solve by induction. At n=1n = 1 the claimed expression is 2⋅2⋅H1−4=0=E12 \cdot 2 \cdot H_1 - 4 = 0 = E_1. Assume En−1=2nHn−1−4(n−1)E_{n-1} = 2nH_{n-1} - 4(n-1) for some n⩾2n \geqslant 2. Substituting into the last recurrence and using Hn−1=Hn−1/nH_{n-1} = H_n - 1/n,

nEn=(n+1)(2nHn−1−4(n−1))+2(n−1)=2n(n+1)Hn−1−4n2+2n+2=2n(n+1)(Hn−1n)−4n2+2n+2=2n(n+1)Hn−4n2.\begin{aligned} nE_n &= (n+1)\bigl(2nH_{n-1} - 4(n-1)\bigr) + 2(n-1) \\ &= 2n(n+1)H_{n-1} - 4n^2 + 2n + 2 \\ &= 2n(n+1)\left(H_n - \frac{1}{n}\right) - 4n^2 + 2n + 2 \\ &= 2n(n+1)H_n - 4n^2 . \end{aligned}

Dividing by nn gives En=2(n+1)Hn−4nE_n = 2(n+1)H_n - 4n.

Step 4: the growth rate. By the bounds on the harmonic numbers, 12⌊log⁡2n⌋⩽Hn⩽1+log⁡2n\tfrac12\lfloor \log_2 n \rfloor \leqslant H_n \leqslant 1 + \log_2 n. The upper bound gives En⩽2(n+1)(1+log⁡2n)=O(nlog⁡n)E_n \leqslant 2(n+1)(1 + \log_2 n) = O(n \log n). The lower bound gives En⩾(n+1)(log⁡2n−1)−4nE_n \geqslant (n+1)(\log_2 n - 1) - 4n, and for all sufficiently large nn the term (n+1)log⁡2n(n+1)\log_2 n exceeds twice the rest, so En=Ω(nlog⁡n)E_n = \Omega(n \log n). Thus En=Θ(nlog⁡n)E_n = \Theta(n \log n).

Example 5.30 (Three keys).

For three distinct keys, a smallest or largest first pivot costs two comparisons and leaves a call on two keys costing one more, for a total of 33. A middle first pivot costs two comparisons and leaves only calls on one key, for a total of 22. The three ranks are equally likely, so E3=(3+2+3)/3=8/3E_3 = (3 + 2 + 3)/3 = 8/3. The formula agrees: 2⋅4⋅(1+12+13)−12=8/32 \cdot 4 \cdot \bigl(1 + \tfrac12 + \tfrac13\bigr) - 12 = 8/3.

Problem 5.12.

Show that on the input (1,2,…,n)(1, 2, \ldots, n) quicksort with the first record always taken as pivot makes n(n−1)/2n(n-1)/2 comparisons, and that with a uniformly random pivot the probability of making all n(n−1)/2n(n-1)/2 is 2n−1/n!2^{n-1}/n! for n⩾2n \geqslant 2.

Problem 5.13.

Two keys of ranks i<ji < j are compared by randomized quicksort exactly when the first pivot chosen from the keys of ranks i,…,ji, \ldots, j is one of the two. Deduce that they are compared with probability 2/(j−i+1)2/(j - i + 1), and use linearity of expectation to give a second proof that En=2(n+1)Hn−4nE_n = 2(n+1)H_n - 4n.

Lower Bounds for Comparison Sorting

A deterministic comparison sort is described, like a comparison search, by a binary decision tree: each internal vertex is a comparison, the two branches below it are its two outcomes, and each leaf records the rearrangement the algorithm outputs. An algorithm may ask a comparison whose result is already forced by earlier answers, and then one branch below it is reached by no input.

a₀ < a₁?a₀ < a₂?a₁ < a₂?a₁ < a₂?a₁ < a₀?a₀ < a₂?a₁ < a₀?012021×201102120210×yesno
Figure 5.6. A comparison tree for three records a0,a1,a2a_0, a_1, a_2. Left branches answer yes and right branches no; a leaf such as 021021 means a0<a2<a1a_0 \lt a_2 \lt a_1. The two accented leaves cannot be reached, because the comparison above them was already settled by an earlier answer.

The comparison tree of Figure 5.6 names the compared records a0,a1,a2a_0, a_1, a_2 rather than their changing positions in the array. Its six reachable leaves are the six possible strict orders; the crossed leaves are impossible by transitivity.

Theorem 5.31 (Worst-case lower bound for sorting).

Every deterministic sorting algorithm that learns about nn distinct, otherwise arbitrary keys only by comparing pairs makes at least ⌈log⁡2(n!)⌉\lceil \log_2(n!) \rceil comparisons on some input. Hence its worst-case number of comparisons is Ω(nlog⁡n)\Omega(n \log n).

Discussion.

As for searching, the proof counts outputs, and there are now n!n! of them. Two inputs whose keys are in different relative orders need different rearrangements, so they must reach different leaves, and the decision tree has at least n!n! reachable leaves. The corollary on heights of binary trees then bounds the height below by log⁡2(n!)\log_2(n!). For the growth rate, half of the factors of n!n! are at least n/2n/2, so log⁡2(n!)\log_2(n!) is at least about n2log⁡2n2\tfrac{n}{2}\log_2 \tfrac{n}{2}.

Proof.

There are n!n! possible relative orders of the distinct input keys: nn choices for the rank of the first record, n−1n - 1 for the second, and so on. Represent the algorithm by its binary decision tree. Each input follows one path from the root to a leaf, and the length of that path is its number of comparisons. Two different relative orders cannot end at the same leaf: that leaf prescribes one output rearrangement for both, and for at least one of them it is wrong. Therefore the tree has at least n!n! reachable leaves, and after removing the unreachable branches it is a binary tree with at least n!n! leaves. By the corollary on heights its height hh satisfies h⩾⌈log⁡2(n!)⌉h \geqslant \lceil \log_2(n!) \rceil.

The last ⌊n/2⌋\lfloor n/2 \rfloor factors of n!=n(n−1)⋯1n! = n(n-1)\cdots 1 are each at least n/2n/2, so n!⩾(n/2)⌊n/2⌋n! \geqslant (n/2)^{\lfloor n/2 \rfloor} and

log⁡2(n!)⩾⌊n/2⌋log⁡2(n/2)=Ω(nlog⁡n).\log_2(n!) \geqslant \lfloor n/2 \rfloor \log_2(n/2) = \Omega(n \log n) .

Removing the branches that no input can follow, and contracting each vertex left with one child, turns the decision tree into a full binary tree with one leaf per possible outcome.

before pruningafter pruninga₁a₂a₃a₄impossibleimpossiblea₁a₂a₃a₄
Figure 5.7. Pruning. Branches that no input can follow are drawn dashed on the left; removing them and contracting each vertex left with a single child gives the full binary tree on the right, with one leaf for each of the four possible outcomes.

Merge sort makes at most n⌈log⁡2n⌉n\lceil \log_2 n\rceil comparisons, so it is asymptotically optimal among comparison sorts in the worst case.

The mean number of comparisons over the n!n! equally weighted orders of distinct keys is the mean depth of the leaves of the decision tree, one leaf per order. A full binary tree with KK leaves is balanced if its leaf depths differ by at most one.

Proposition 5.32 (Balanced trees minimise the total depth).

Among full binary trees with KK leaves, a balanced one has the least sum of leaf depths, and in a balanced full binary tree with KK leaves every leaf has depth at least ⌊log⁡2K⌋\lfloor \log_2 K \rfloor.

Discussion.

The first claim is proved by an exchange that lowers the depth sum of an unbalanced tree. If some leaf cc is at least two levels above the deepest leaves, move a deepest pair of sibling leaves a,ba, b together with their parent into the place of cc, and put cc where their parent was. Three depths change, and the sum falls. The depth sum is a nonnegative integer, so the exchange can be repeated only finitely often, and it stops at a balanced tree. For the second claim, look at the shallowest leaf: every level above it is full, and all leaves lie on its level or the next, which bounds KK by twice the size of its level.

Proof.

Take a full binary tree that is not balanced. Choose deepest sibling leaves a,ba, b, at depth pp, with parent nn, and a leaf cc at depth p′⩽p−2p' \leqslant p - 2. Exchange the subtree consisting of nn, aa and bb with the leaf cc. The tree remains a full binary tree with the same leaves, aa and bb move to depth p′+1p' + 1, and cc moves to depth p−1p - 1. The sum of these three depths changes from 2p+p′2p + p' to 2(p′+1)+(p−1)2(p' + 1) + (p - 1), a decrease of p−p′−1>0p - p' - 1 > 0, and no other depth changes. Repeating the operation reaches a balanced tree, because the nonnegative integer depth sum strictly decreases each time. Every tree with KK leaves can be transformed in this way into a balanced one with no larger depth sum, and all balanced full binary trees with KK leaves have the same multiset of leaf depths, as the count below shows; so a balanced tree has the least sum.

Let the shallowest leaf of a balanced full binary tree have depth dd. There is no leaf above depth dd, so every vertex at depth less than dd is internal with two children, and depth dd holds 2d2^d vertices. The leaves are at depth dd or d+1d + 1, and those at depth d+1d + 1 are the children of the internal vertices at depth dd, so K=2d+#{internal vertices at depth d}K = 2^d + \#\{\text{internal vertices at depth } d\}. At least one vertex at depth dd is a leaf, so 2d⩽K<2d+12^d \leqslant K < 2^{d+1}. This determines d=⌊log⁡2K⌋d = \lfloor \log_2 K \rfloor, and with it the number of leaves at each depth, and every leaf has depth at least ⌊log⁡2K⌋\lfloor \log_2 K \rfloor.

beforeafterc⋯abndepth p′depth p⋯abcnp′ + 1p − 1
Figure 5.8. The exchange. The two deepest sibling leaves aa, bb and their parent nn swap places with a leaf cc at least two levels higher; aa and bb rise to depth p′+1p' + 1 and cc falls to depth p−1p - 1.

For example, six leaves can occur at depths 22 and 33. The proposition gives the asymptotic bound: the mean depth of any full binary tree with KK leaves is at least ⌊log⁡2K⌋\lfloor \log_2 K \rfloor, which is Ω(log⁡K)\Omega(\log K). The exact bound log⁡2K\log_2 K is proved differently.

Theorem 5.33 (Average lower bound for sorting).

Suppose the n!n! relative orders of nn distinct keys are equally likely. Every deterministic comparison sort has mean number of comparisons at least log⁡2(n!)\log_2(n!), and therefore Ω(nlog⁡n)\Omega(n \log n).

Discussion.

The proof turns the leaf depths into lengths of intervals. Each leaf is reached by a word of left and right choices, and a word of length dd picks out a subinterval of [0,1)[0, 1) of length 2−d2^{-d} by halving repeatedly. Since no leaf’s word begins another leaf’s word, the intervals do not overlap, so their lengths add up to at most 11. The mean depth is then bounded below by comparing the arithmetic mean of the numbers 2−di2^{-d_i} with their geometric mean. The inequality of the means was proved for two numbers in Lesson 1; the proof extends it to KK numbers by doubling up to a power of two and padding.

Proof.

Keep one reachable leaf of the decision tree for each of the K=n!K = n! relative orders, and let their depths be d1,…,dKd_1, \ldots, d_K; the mean number of comparisons is K−1∑idiK^{-1}\sum_i d_i. Each path from the root to a leaf is a word of left and right choices. No leaf’s word can be a prefix of another leaf’s word, because a computation stops when it reaches a leaf. To a word of length did_i associate the subinterval of [0,1)[0, 1) of length 2−di2^{-d_i} obtained by choosing the left or right half at each successive letter. The intervals for distinct leaf words do not overlap, so their total length is at most 11:

∑i=1K2−di⩽1.\sum_{i=1}^{K} 2^{-d_i} \leqslant 1 .

Write xi=2−di>0x_i = 2^{-d_i} > 0 and A=K−1∑ixiA = K^{-1}\sum_i x_i, so that A⩽1/KA \leqslant 1/K. We need the inequality of arithmetic and geometric means for KK positive numbers, (∏ixi)1/K⩽A\bigl(\prod_i x_i\bigr)^{1/K} \leqslant A. For two numbers u,vu, v it is uv⩽(u+v)/2\sqrt{uv} \leqslant (u + v)/2. For a list of 2r2^r numbers, split it into two equal halves with geometric means G1,G2G_1, G_2 and arithmetic means A1,A2A_1, A_2; by induction on rr, G1⩽A1G_1 \leqslant A_1 and G2⩽A2G_2 \leqslant A_2, so the whole list has geometric mean G1G2⩽A1A2⩽(A1+A2)/2\sqrt{G_1 G_2} \leqslant \sqrt{A_1 A_2} \leqslant (A_1 + A_2)/2, its arithmetic mean. For general KK, choose a power of two N⩾KN \geqslant K and append N−KN - K copies of AA to the list x1,…,xKx_1, \ldots, x_K. The enlarged list still has arithmetic mean AA, so the power-of-two case gives (∏ixi)AN−K⩽AN\bigl(\prod_i x_i\bigr) A^{N-K} \leqslant A^N, and cancelling the positive factor AN−KA^{N-K} gives ∏ixi⩽AK\prod_i x_i \leqslant A^K.

Substituting xi=2−dix_i = 2^{-d_i},

2−1K∑idi⩽A⩽1K.2^{-\frac{1}{K}\sum_i d_i} \leqslant A \leqslant \frac{1}{K} .

Taking base-two logarithms and multiplying by −1-1, which reverses the inequality, gives K−1∑idi⩾log⁡2K=log⁡2(n!)K^{-1}\sum_i d_i \geqslant \log_2 K = \log_2(n!). Finally log⁡2(n!)⩾⌊n/2⌋log⁡2(n/2)=Ω(nlog⁡n)\log_2(n!) \geqslant \lfloor n/2 \rfloor \log_2(n/2) = \Omega(n \log n), as in the worst-case bound.

Example 5.34 (Six outcomes).

With six equally likely outcomes, a full binary tree may have two leaves at depth 22 and four at depth 33. Its mean depth is (2⋅2+4⋅3)/6=8/3(2 \cdot 2 + 4 \cdot 3)/6 = 8/3, which is at least log⁡26≈2.585\log_2 6 \approx 2.585. The same leaf-depth argument applies to the n!n! possible input orders.

Sorting algorithms that are faster than nlog⁡nn \log n exist, such as counting sort and radix sort, but only because they use information about the keys beyond pairwise comparison, for instance that they are small integers usable as array indices.

Problem 5.14.

Draw a decision tree for insertion sort on three distinct keys a0,a1,a2a_0, a_1, a_2, and give its height and the mean depth of its leaves. Compare both with ⌈log⁡23!⌉\lceil \log_2 3! \rceil and log⁡23!\log_2 3!.

Problem 5.15.

Show that five distinct keys can be sorted with 77 comparisons in the worst case, and that no comparison sort does it with 66.

Sorted Arrays as Sets

A sorted array implements the set interface. Building it is sorting, O(nlog⁡n)O(n \log n) by merge sort. find(k) is binary search, O(log⁡n)O(\log n). The smallest and largest keys are at the two ends, and find_next(k) and find_prev(k) are binary searches for the position where kk would go. Inserting or deleting while keeping the order shifts up to nn items.

Data structurebuild(X)find(k)insert(x), delete(k)find_min(), find_max()find_prev(k), find_next(k)
Arraynnnnnnnnnn
Sorted arraynlog⁡nn \log nlog⁡n\log nnn11log⁡n\log n
Direct access arrayuu1111uuuu
Hash tablenn (e)11 (e)11 (a)(e)nnnn

Each entry is an O(⋅)O(\cdot) bound; (e) marks an expected bound and (a) an amortized one. For the sorted array, find is optimal among comparison algorithms by the lower bound for searching, and the bound does not apply to the hash table, which is not a comparison algorithm.

Problem 5.16.

Implement the set interface on a sorted array as a class Sorted_Array_Set, using merge_sort in build and binary search in find, find_next and find_prev, with the running times of the table.

Exercises on Connectivity and Trees

Exercise 5.1.

How many spanning subgraphs of K4K_4 are trees?

Exercise 5.2.

Prove that every connected graph with at least two vertices has a vertex that is not an articulation vertex.

Exercise 5.3.

Let n⩾2n \geqslant 2, and let d1⩽d2⩽⋯⩽dnd_1 \leqslant d_2 \leqslant \cdots \leqslant d_n be integers. Show that there is a tree with vertex degrees d1,…,dnd_1, \ldots, d_n if and only if d1⩾1d_1 \geqslant 1 and ∑idi=2n−2\sum_i d_i = 2n - 2.

Exercise 5.4.

Let T1,…,TkT_1, \ldots, T_k be subtrees of a tree TT, meaning subgraphs that are trees, any two of which have at least one vertex in common. Prove that some vertex lies in every TiT_i.

Exercise 5.5.

The radius of a connected graph is min⁡vmax⁡wd(v,w)\min_{v} \max_{w} d(v, w), the least over all vertices vv of the greatest distance from vv to another vertex. Let CnC_n be the cycle on nn vertices, with vertices 1,…,n1, \ldots, n and edges {i,i+1}\{i, i+1\} and {n,1}\{n, 1\}. Find the radius and the diameter of K6K_6, of C9C_9 and of K5,7K_{5,7}.

Exercise 5.6.

A directed graph GG has 10001000 vertices. Its underlying undirected graph has one connected component of size 700700, and GG has one strongly connected component of size 500500; its other strongly connected components are smaller.

  1. Suppose 650650 vertices are reachable from a vertex uu. Is uu in the component of size 700700? Explain.
  2. Is uu in the strongly connected component of size 500500? Explain.
  3. Suppose 600600 vertices are reachable from a vertex vv, and 600600 vertices are reachable from vv once every arc is reversed. Is vv in the strongly connected component of size 500500? Explain.
  4. Prove that for every vertex ww of the component of size 700700, there is a directed walk from vv to ww or one from ww to vv.

Exercise 5.7.

The divisibility graph D(n)D(n) has vertices 1,2,…,n1, 2, \ldots, n and an edge between i≠ji \neq j whenever one of them divides the other.

  1. Draw D(12)D(12). How many connected components does it have, and what is the size of its largest clique?
  2. Prove that for every d⩾2d \geqslant 2, a graph with no loops in which every vertex has degree at least dd contains a simple cycle through at least d+1d + 1 vertices.

Exercises on Searching

Exercise 5.8.

A narrow island runs north–south for nn kilometres, and a searcher must locate a friend to the nearest kilometre. A tracking device tells the searcher whether the friend is north or south of the current position, but not how far, and a teleporter jumps to any given kilometre in constant time. If the friend is kk kilometres from the nearer end of the island, at kilometre kk or n−kn - k, describe an algorithm that finds the friend after visiting O(log⁡k)O(\log k) locations.

Exercise 5.9.

A ridge is given as an array of nn distinct altitudes. A point is a good collection point if it is lower than all its neighbours in the array. Design an algorithm running in O(log⁡n)O(\log n) time that finds a good collection point, prove it correct, and prove its running time.

Exercises on Sorting

Exercise 5.10.

Implementations of insertion sort and merge sort run on the same machine. On inputs of size nn, insertion sort takes 8n28n^2 steps and merge sort takes 64 nlog⁡2n64\, n \log_2 n steps. For which values of nn does insertion sort beat merge sort?

Exercise 5.11.

Person ii, for i∈{1,…,n}i \in \{1, \ldots, n\}, enters a room at time aia_i and leaves at time bi>aib_i > a_i, all the ai,bia_i, b_i being distinct. The lights are off at the start of the day; the first person to enter switches them on, and a person who leaves an empty room switches them off. Given (a1,b1),…,(an,bn)(a_1, b_1), \ldots, (a_n, b_n), we want the number of times the lights are switched on. Design, and prove correct and costed,

  1. a Θ(n2)\Theta(n^2) algorithm, and
  2. an O(nlog⁡n)O(n \log n) algorithm.

Exercise 5.12.

For each scenario choose selection sort, insertion sort or merge sort, and justify the choice by asymptotic running time.

  1. A data structure DD maintains an extrinsic order on nn items, with D.get_at(i) in worst-case Θ(1)\Theta(1) time and D.set_at(i, x) in worst-case Θ(nlog⁡n)\Theta(n \log n) time. Sort the items of DD in place.
  2. A static array holds references to nn comparable objects, any two of which take Θ(log⁡n)\Theta(\log n) time to compare. Sort the references so that the objects appear in nondecreasing order.
  3. A sorted array of nn integers, each fitting in a machine word, has had log⁡log⁡n\log \log n exchanges made between pairs of adjacent items. Re-sort it.

Exercise 5.13.

  1. Describe the principle of merge sort, and show the steps it takes to sort the array (9,3,6,2,4,1,5)(9, 3, 6, 2, 4, 1, 5).
  2. Insertion sort can be seen as a merge sort in which each step splits an array of size nn into one of size 11, the element to be inserted, and one of size n−1n - 1. By solving the appropriate recurrence, show that this recursive insertion sort takes O(n2)O(n^2) time, assuming that merging two arrays takes O(n)O(n) time.
  3. Show that merge sort on a linked list takes O(nlog⁡n)O(n \log n) time, and that it can be done with O(1)O(1) auxiliary space apart from the recursion, showing how. A programmer who can merge arrays only with O(n)O(n) extra space proposes to convert arrays to linked lists before sorting them, to save space. Comment on this strategy.

Exercise 5.14.

  1. Suppose that quicksort always partitions into two parts of relative sizes α\alpha and 1−α1 - \alpha, for a constant 0<α<120 < \alpha < \tfrac12. Ignoring rounding, find the least depth of a leaf in the recursion tree as a function of nn and α\alpha.
  2. How long does the quicksort of these notes take if all the keys are equal? Explain.
  3. What are the advantages and disadvantages of choosing the pivot at random? How does it affect the worst-case and the average-case running time?

Exercise 5.15.

How would you construct an input that makes randomized quicksort take quadratic time, without access to the state of the random number generator?

Exercise 5.16.

Merge sort can be implemented as the usual two-way merge sort, or as a three-way merge sort that splits its input into three and sorts each part recursively.

  1. Find the worst-case number of comparisons needed to merge two sorted arrays of length n/2n/2.
  2. Find the worst-case number of comparisons needed to merge three sorted arrays of length n/3n/3, both by merging all three at once and by merging in pairs.
  3. Using these, and solving suitable recurrences, find the total number of comparisons made by two-way and by three-way merge sort.
  4. If comparisons dominate the cost, which would you expect to be faster on an arbitrary array?

Exercise 5.17.

Describe an algorithm, running in time strictly better than O(n2)O(n^2), that takes a positive integer ss and a set AA of nn positive integers and decides whether two distinct elements of AA add up to exactly ss. Give its running time.

Exercise 5.18.

Each of nn computers has run the same computation, and we want to know whether strictly more than n/2n/2 of them arrived at the same result. The only available query takes two computers and reports whether they produced the same result. Design an algorithm that decides this with O(nlog⁡n)O(n \log n) queries, prove it correct, and prove the bound on the number of queries.

Exercise 5.19.

Find an asymptotically tight upper bound for the recurrence T(1)=1T(1) = 1, T(n)=T(n−1)+log⁡2nT(n) = T(n-1) + \log_2 n, and explain your answer.

Check Yourself

 

Fresh questions on the whole lesson — none of them is worked out above. Work each one out on paper before opening Python; the box only tells you whether you got there.

Answers are checked in your browser, as often as you like. Nothing is sent anywhere and nothing is kept but your own work. A formula may be written with the symbols themselves or with ~ & | -> <-> ^, and \and, \or, \to expand as you type.

Exercise 5.20.

A forest has 2020 vertices and 44 components. How many edges does it have?

answer one of these

Exercise 5.21.

What is the largest number of leaves a binary tree of height 55 can have?

answer one of these

Exercise 5.22.

What is the least height of a binary tree with 2020 vertices?

answer one of these

Exercise 5.23.

At most how many passes does binary search make on a sorted array of length 100100?

answer one of these

Exercise 5.24.

How many comparisons does linear search make on an array of 1212 entries that does not contain xx?

answer one of these

Exercise 5.25.

How many inversions does the array (4,1,3,2)(4, 1, 3, 2) have?

answer one of these

Exercise 5.26.

How many comparisons does selection sort make on an array of 88 records?

answer one of these

Exercise 5.27.

What is the worst-case number of key comparisons made by merge sort on 1616 records?

answer one of these

Exercise 5.28.

What is the expected number E4E_4 of comparisons made by randomized quicksort on four distinct keys?

answer one of these

Exercise 5.29.

What is the smallest integer hh with 2h⩾4!2^h \geqslant 4!?

answer one of these

Exercise 5.30.

In how many relative orders can 55 distinct keys arrive?

answer one of these

Exercise 5.31.

Which of the three quadratic sorts of these notes is not stable as implemented?

answer one of these

Exercise 5.32.

At most how many comparisons does quicksort make on 66 distinct keys?

answer one of these

Exercise 5.33.

At most how many comparisons does merging sorted arrays of lengths 33 and 55 take?

answer one of these

Exercise 5.34.

How many comparisons does bubble sort with the early stop make on an already sorted array of 1010 records?

answer one of these

Exercise 5.35.

How many shifts does insertion sort make on (5,4,3,2,1)(5, 4, 3, 2, 1)?

answer one of these