Algorithm Search⌘K

Proofs: Indexed priority queue

These are the human proofs of Indexed priority queue, at full length.

The indexed priority queue keeps its heap and its position map full

This proof shows: correctness, termination.

Write p(j)=⌊(j−1)/2⌋p(j) = \lfloor (j - 1)/2 \rfloor and K(j)=key[pq[j]]K(j) = \mathit{key}[\mathit{pq}[j]] for a position 0≤j<n0 \le j < n. Let SS be the set of items in the queue.

Invariants. Between operations:

(H) K(p(j))≤K(j)K(p(j)) \le K(j) for each 1≤j<n1 \le j < n;

(P) pq[0…n−1]\mathit{pq}[0 \ldots n - 1] lists the items of SS, each once; qp[pq[j]]=j\mathit{qp}[\mathit{pq}[j]] = j for each 0≤j<n0 \le j < n; and qp[i]=−1\mathit{qp}[i] = -1 for each i∉Si \notin S.

Both hold for the empty queue, where n=0n = 0 and qp\mathit{qp} is all −1-1.

Lemma 1 (EXCH). If qp[pq[j]]=j\mathit{qp}[\mathit{pq}[j]] = j for each j<nj < n before EXCH(a,b)(a, b) with a,b<na, b < n, then the same holds after it, and the set of items in pq[0…n−1]\mathit{pq}[0 \ldots n - 1] is unchanged.

Proof. Line L13 swaps two cells of pq\mathit{pq}, so the set is the same. The positions j∉{a,b}j \notin \{a, b\} keep their item, and that item's entry in qp\mathit{qp} is unchanged, because the two entries written belong to the items now at aa and bb, which are distinct from the item at jj. Those two entries are set to aa and bb. □\square

So (P) is kept by any sequence of EXCH calls. SWIM and SINK change pq\mathit{pq} only through EXCH, and they change no key.

Lemma 2 (SWIM). Suppose (P) holds, and (H) holds for each j≠tj \ne t, and if t≥1t \ge 1 then K(p(t))≤K(c)K(p(t)) \le K(c) for each child cc of tt. Then SWIM(t)(t) stops after at most ⌊lg⁡(t+1)⌋\lfloor \lg(t + 1) \rfloor iterations, and after it (H) and (P) hold.

Lemma 3 (SINK). Suppose (P) holds, and (H) holds for each jj that is not a child of tt, and if t≥1t \ge 1 then K(p(t))≤K(c)K(p(t)) \le K(c) for each child cc of tt. Then SINK(t)(t) stops after at most ⌊lg⁡n⌋\lfloor \lg n \rfloor iterations, and after it (H) and (P) hold.

Proofs. These are the sift-up and sift-down lemmas of a binary heap applied to the array K(0…n−1)K(0 \ldots n - 1): by Lemma 1, EXCH(a,b)(a, b) swaps K(a)K(a) and K(b)K(b) and keeps (P). The invariant of SWIM is that each edge (p(j),j)(p(j), j) with j≠tj \ne t holds and that K(p(t))K(p(t)), if it exists, is at most each child key of tt; a swap at a failing edge moves the possible failure from tt to p(t)p(t). The invariant of SINK is that each edge except those from tt to its children holds, and that K(p(t))K(p(t)), if it exists, is at most each child key of tt; a swap with the smaller child cc moves the possible failure to the edges out of cc. Each iteration moves tt one level, so the bounds on the iterations follow. The details are those of the proofs on the pages heap increase-key and heap extract-max, with the order reversed. □\square

INSERT(i,k)(i, k) with i∉Si \notin S. Line L2 appends ii at position nn and sets qp[i]=n\mathit{qp}[i] = n, so (P) holds for S∪{i}S \cup \{i\}. The new cell is a leaf, so (H) holds except at the edge into it, and the new cell has no children; Lemma 2 applies.

DECREASE-KEY(i,k)(i, k) with k≤key[i]k \le \mathit{key}[i] and t=qp[i]t = \mathit{qp}[i]. Lowering K(t)K(t) keeps the edges from tt to its children. The edge into tt may fail, and K(p(t))K(p(t)) is at most the old K(t)K(t) and so at most each child key. Lemma 2 applies. CHANGE-KEY with a larger key is the mirror case: the edge into tt still holds, and only the edges out of tt can fail. The precondition of Lemma 3 asks that K(p(t))K(p(t)) is at most each child key of tt, which holds since K(p(t))K(p(t)) is at most the old K(t)K(t), which was at most each child key. Lemma 3 applies.

DELETE(i)(i) with j=qp[i]j = \mathit{qp}[i]. Line L10 swaps the cells jj and n−1n - 1 by EXCH and drops the last cell, which now holds ii; line L12 sets qp[i]=−1\mathit{qp}[i] = -1. So (P) holds for S∖{i}S \setminus \{i\}. If j=n−1j = n - 1, the dropped cell was a leaf, and (H) holds. Else the cell jj holds the old last item yy, and only the edges at jj can fail. If K(j)<K(p(j))K(j) < K(p(j)), the children of jj were at least the old key at jj, which was at least K(p(j))>K(j)K(p(j)) > K(j), so the edges out of jj hold, and Lemma 2 applies to SWIM(j)(j); the item then moves up, and the later SINK finds a cell whose children are not smaller, by (H), and stops at once. Else the edge into jj holds, and K(p(j))K(p(j)) is at most the old key at jj, which was at most each child key, so SWIM(j)(j) does nothing and Lemma 3 applies to SINK(j)(j).

EXTRACT-MIN. If n≥1n \ge 1, (H) and transitivity along the path to the root give K(0)≤K(j)K(0) \le K(j) for each jj, so i=pq[0]i = \mathit{pq}[0] has the smallest key. Lines L5 and L6 are DELETE(i)(i) with j=0j = 0, where the root has no parent, so only SINK is needed, by the same case analysis. Line L7 returns ii and its key.

Theorem. Each operation stops in O(log⁡n)O(\log n) time, keeps (H) and (P), and does what its name says on the set SS and the keys; EXTRACT-MIN returns an item of smallest key, and CONTAINS(i)(i), which tests qp[i]≠−1\mathit{qp}[i] \ne -1, is correct by (P).

Proof. This collects the cases above, with Lemmas 2 and 3 for the time. ■\blacksquare

Back to Indexed priority queue