Algorithm Search⌘K

Proofs: d-ary heap

These are the human proofs of d-ary heap, at full length.

The costs of the d-ary heap operations full

This proof shows: time.worst, space.

Let d≥2d \ge 2, n≥1n \ge 1, and let hh be the height of the tree, the largest depth of a node.

Lemma 1 (levels). The nodes at depth tt are the indices j≤nj \le n with (dt−1)/(d−1)<j≤(dt+1−1)/(d−1)(d^t - 1)/(d - 1) < j \le (d^{t+1} - 1)/(d - 1). So hh is the least integer with (dh+1−1)/(d−1)≥n(d^{h+1} - 1)/(d - 1) \ge n, and h<log⁡dn+1h < \log_d n + 1.

Proof. By induction on tt. Depth 0 is the index 1. Let depth tt be the range a<j≤ba < j \le b, with a=(dt−1)/(d−1)a = (d^t - 1)/(d - 1) and b=(dt+1−1)/(d−1)b = (d^{t+1} - 1)/(d - 1). The children of these nodes are the indices from d(a+1−1)+2=da+2d(a + 1 - 1) + 2 = da + 2 to db+1db + 1, and da+1=bda + 1 = b and db+1=(dt+2−1)/(d−1)db + 1 = (d^{t+2} - 1)/(d - 1), so depth t+1t + 1 is the range b<j≤(dt+2−1)/(d−1)b < j \le (d^{t+2} - 1)/(d - 1). A node at depth hh exists, so (dh−1)/(d−1)≤n−1(d^h - 1)/(d - 1) \le n - 1, which gives dh≤(d−1)(n−1)+1<(d−1)n+1≤dnd^h \le (d - 1)(n - 1) + 1 < (d - 1) n + 1 \le d n. So h<log⁡dn+1h < \log_d n + 1. □\square

Insert and decrease-key. SIFT-UP does one comparison and at most one swap per level and goes up from depth at most hh, so it costs O(h+1)=O(log⁡dn+1)O(h + 1) = O(\log_d n + 1) by Lemma 1.

Extract-min. SIFT-DOWN goes down at most hh levels, and each iteration of line L6 does at most dd comparisons in lines L8 to L10. So it does at most d(h+1)d(h + 1) comparisons, O(dlog⁡dn)O(d \log_d n) for n≥2n \ge 2.

Lemma 2 (sum of heights). Let NtN_t be the number of nodes of height at least tt. Then Nt≤(n−1)/dt+1N_t \le (n - 1)/d^t + 1 for 1≤t≤h1 \le t \le h, and ∑jheight(j)≤(n−1)/(d−1)+h\sum_{j} \mathrm{height}(j) \le (n - 1)/(d - 1) + h.

Proof. A node jj has height at least tt exactly when its first descendant tt levels down exists. The first child of jj is d(j−1)+2d(j - 1) + 2, so with g(j)=j−1+1/(d−1)g(j) = j - 1 + 1/(d - 1) we get g(first child)=d g(j)g(\text{first child}) = d\,g(j), and the first descendant tt levels down, Lt(j)L_t(j), has g(Lt(j))=dtg(j)g(L_t(j)) = d^t g(j). So Lt(j)≤nL_t(j) \le n means dt(j−1)+dt/(d−1)≤n−1+1/(d−1)d^t (j - 1) + d^t/(d - 1) \le n - 1 + 1/(d - 1), which implies j−1≤(n−1)/dtj - 1 \le (n - 1)/d^t. The nodes with this property are 1,…,Nt1, \ldots, N_t, so Nt≤(n−1)/dt+1N_t \le (n - 1)/d^t + 1. Then

∑jheight(j)=∑t=1hNt≤∑t=1h(n−1dt+1)<n−1d−1+h.□\sum_j \mathrm{height}(j) = \sum_{t=1}^{h} N_t \le \sum_{t=1}^{h} \left( \frac{n - 1}{d^t} + 1 \right) < \frac{n - 1}{d - 1} + h. \quad \square

Build. Line L22 runs SIFT-DOWN once at each internal node. At a node of height tt, each iteration of line L6 that does a comparison is at a node with a child, and there are at most tt such iterations, one per level. So the sift does at most d td\,t comparisons. With Lemma 2 and Lemma 1, build does at most

d∑jheight(j)≤d(n−1)d−1+d h<2n+d(log⁡dn+1)d \sum_j \mathrm{height}(j) \le \frac{d(n - 1)}{d - 1} + d\,h < 2n + d(\log_d n + 1)

comparisons, and its time is OO of the same bound, so build is O(n)O(n) for each d≤nd \le n.

Space. The array has nn cells, and each operation uses O(1)O(1) extra cells.

Theorem. In a dd-ary heap of nn keys, insert and decrease-key take O(log⁡dn+1)O(\log_d n + 1) time, extract-min takes O(dlog⁡dn+d)O(d \log_d n + d) time, find-min takes O(1)O(1) time, and build takes O(n+dlog⁡dn+d)O(n + d \log_d n + d) time, which is O(n)O(n) for d≤nd \le n.

Proof. The cases above. ■\blacksquare

The operations of a d-ary heap keep the heap property full

This proof shows: correctness, termination.

Let d≥2d \ge 2. Write p(j)=⌊(j−2)/d⌋+1p(j) = \lfloor (j - 2)/d \rfloor + 1 for j≥2j \ge 2 and C(j)C(j) for the set of children d(j−1)+2,…,dj+1d(j - 1) + 2, \ldots, dj + 1 of jj that are at most nn.

The tree. For each j≥2j \ge 2, jj is a child of p(j)p(j), and each c∈C(i)c \in C(i) has p(c)=ip(c) = i: write c=d(i−1)+1+rc = d(i - 1) + 1 + r with 1≤r≤d1 \le r \le d; then c−2=d(i−1)+(r−1)c - 2 = d(i - 1) + (r - 1) with 0≤r−1≤d−10 \le r - 1 \le d - 1, so ⌊(c−2)/d⌋=i−1\lfloor (c - 2)/d \rfloor = i - 1. Also p(j)<jp(j) < j for j≥2j \ge 2. So the formulas define a rooted tree on {1,…,n}\{1, \ldots, n\} with root 1, in which each node has at most dd children, and the index of each node is larger than the index of its parent.

An edge is a pair (p(j),j)(p(j), j) with 2≤j≤n2 \le j \le n, and it holds when A[p(j)]≤A[j]A[p(j)] \le A[j]. The invariant (H) says that each edge holds.

Lemma 1 (sift-up). If each edge (p(j),j)(p(j), j) with j≠ij \ne i holds and, when i≥2i \ge 2, A[p(i)]≤A[c]A[p(i)] \le A[c] for each c∈C(i)c \in C(i), then SIFT-UP(A,i)(A, i) stops after at most the depth of ii iterations, only swaps keys, and afterwards (H) holds.

Proof. The proof of Lemma 1 on the binary heap proof page uses only these facts: the node ii has one parent qq, the swap of line L4 exchanges A[i]A[i] and A[q]A[q] when A[i]<A[q]A[i] < A[q], the other children of qq held their edges to qq, and the edges out of ii are covered by the second part of the hypothesis. All of them hold in a tree with any number of children per node, with "each other child ss of qq" in place of "the other child". Each iteration moves ii to its parent, so it stops after at most the depth of ii iterations, at the root or when the edge into ii holds. □\square

Lemma 2 (sift-down). If each edge (p(j),j)(p(j), j) with p(j)≠ip(j) \ne i holds and, when i≥2i \ge 2, A[p(i)]≤A[c]A[p(i)] \le A[c] for each c∈C(i)c \in C(i), then SIFT-DOWN(A,n,i)(A, n, i) stops after at most the height of ii swaps, changes only cells of the subtree of ii, and afterwards (H) holds.

Proof. Lines L7 to L10 set mm to an index of a smallest key among A[i]A[i] and A[c]A[c] for c∈C(i)c \in C(i): the loop goes over all children CHILD(i,1),…,CHILD(i,d)\mathrm{CHILD}(i, 1), \ldots, \mathrm{CHILD}(i, d), skips those beyond nn, and keeps the index of the least key seen, with m=im = i at the start. If m=im = i, the edges out of ii hold, and with the hypothesis each edge holds. Else let x=A[i]>y=A[m]x = A[i] > y = A[m], with y≤A[s]y \le A[s] for each other child ss of ii. After the swap of line L12, the edge (i,m)(i, m) holds since y≤xy \le x, each edge (i,s)(i, s) holds since y≤A[s]y \le A[s], and the edge (p(i),i)(p(i), i), if i≥2i \ge 2, holds since the hypothesis gave A[p(i)]≤yA[p(i)] \le y. The hypothesis holds again at mm: its parent ii now holds yy, and the children cc of mm had y≤A[c]y \le A[c], since the edges out of mm were not excluded. Each swap moves ii one level down in the subtree of the first ii, so there are at most as many swaps as its height. □\square

Insert. After line L13 the new cell n+1n + 1 is a leaf, and every old edge holds, so Lemma 1 applies to i=n+1i = n + 1, and (H) holds after line L14. The keys are the old keys and kk.

Find-min and extract-min. By (H) and induction along the path from the root, A[1]≤A[j]A[1] \le A[j] for each jj. So A[1]A[1] is a minimum. Line L16 saves it, moves A[n]A[n] to the root and drops the cell nn. Every edge not out of the root joins two unchanged cells, so it holds, and the root has no parent. So Lemma 2 applies to i=1i = 1, and (H) holds after line L17. The keys are the old keys with one copy of the minimum removed.

Decrease-key. If k≤A[i]k \le A[i], the edges out of ii still hold after line L20 writes kk, the old edges give A[p(i)]≤A[i]≤A[c]A[p(i)] \le A[i] \le A[c] for each c∈C(i)c \in C(i), and every other edge is unchanged. So Lemma 1 applies, and (H) holds.

Build. Before the iteration of line L22 for ii, the subtree of each j>ij > i is a heap. If n≤1n \le 1 there is no edge and nothing to do. Else, at the start, i=p(n)i = p(n), and each j>p(n)j > p(n) has no children: d(p(n)−1)=d⌊(n−2)/d⌋≥n−1−dd(p(n) - 1) = d\lfloor (n - 2)/d \rfloor \ge n - 1 - d, so the first child of jj is d(j−1)+2≥d p(n)+2≥n+1d(j - 1) + 2 \ge d\,p(n) + 2 \ge n + 1. So the subtree of jj has no edges. In the iteration for ii, the children of ii have larger indices, so their subtrees are heaps, and Lemma 2 applied to the subtree of ii makes it a heap. Lemma 2 changes only cells of that subtree. A j>ij > i is not an ancestor of ii, so the subtree of jj is either inside the subtree of ii, and then it is part of a heap, or disjoint from it, and then it did not change. After i=1i = 1, the whole tree is a heap.

Theorem. Each operation keeps (H), FIND-MIN and EXTRACT-MIN return a minimum, the keys change as the priority queue type says, and each operation stops.

Proof. The cases above with Lemmas 1 and 2. ■\blacksquare

Back to d-ary heap