Algorithm Search⌘K

Proofs: BST floor and ceiling

These are the human proofs of BST floor and ceiling, at full length.

BST floor and ceiling return the largest key at most k and the smallest key at least k full

This proof shows: correctness, termination.

Let TT be a binary search tree with root rr and distinct keys, with the strict binary search tree property, and let kk be a key. For a node xx (or NIL) write D(x)D(x) for the set of keys in the subtree rooted at xx, with D(NIL)=∅D(\mathrm{NIL}) = \emptyset. For a finite set AA of keys, write f(A)=max⁡{a∈A:a≤k}f(A) = \max\{a \in A : a \le k\}, with $f(A) = $ NIL when no element of AA is at most kk. Treat NIL as smaller than each key, so that max⁡(NIL,a)=a\max(\mathrm{NIL}, a) = a. Then f(A∪B)=max⁡(f(A),f(B))f(A \cup B) = \max(f(A), f(B)) for all finite A,BA, B.

We prove that FLOOR(r,k)(r, k) returns f(D(r))f(D(r)). The proof for CEILING is the same with the order reversed.

Invariant. Before each test of line L2:

(I)f(D(r))=max⁡(key(best),f(D(x))),\text{(I)} \qquad f(D(r)) = \max(\mathrm{key}(best), f(D(x))),

where $\mathrm{key}(\mathrm{NIL}) = $ NIL.

Initialization. Before the first test, $best = $ NIL and x=rx = r, so the right side is f(D(r))f(D(r)).

Maintenance. Suppose (I) holds, $x \ne $ NIL, and line L3 does not return, so k≠x.keyk \ne x.key. Write L=D(x.left)L = D(x.left) and R=D(x.right)R = D(x.right), so D(x)=L∪{x.key}∪RD(x) = L \cup \{x.key\} \cup R, and by the BST property each key of LL is less than x.keyx.key and each key of RR is greater.

  • Case k<x.keyk < x.key (line L4). Each key in {x.key}∪R\{x.key\} \cup R is at least x.key>kx.key > k, so $f({x.key} \cup R) = $ NIL, and f(D(x))=f(L)f(D(x)) = f(L). Line L4 sets xx to x.leftx.left and leaves bestbest alone, so (I) holds after the step.
  • Case x.key<kx.key < k (line L5). Then f({x.key})=x.keyf(\{x.key\}) = x.key, and each key of LL is less than x.keyx.key, so f(L)≤x.keyf(L) \le x.key and f(L∪{x.key})=x.keyf(L \cup \{x.key\}) = x.key. Hence f(D(x))=max⁡(x.key,f(R))f(D(x)) = \max(x.key, f(R)). Also key(best)≤x.key\mathrm{key}(best) \le x.key if $best \ne $ NIL: by Lemma A below, bestbest is a proper ancestor of xx at which the path went right, so xx is in its right subtree and x.key>best.keyx.key > best.key. So
max⁡(key(best),f(D(x)))=max⁡(key(best),x.key,f(R))=max⁡(x.key,f(R)),\max(\mathrm{key}(best), f(D(x))) = \max(\mathrm{key}(best), x.key, f(R)) = \max(x.key, f(R)),

and after line L5 sets best←xbest \leftarrow x and x←x.rightx \leftarrow x.right, the right side of (I) is max⁡(x.keyold,f(R))\max(x.key_{\text{old}}, f(R)), the same value. So (I) holds.

Lemma A. At each test of line L2 with $best \ne $ NIL, bestbest is a proper ancestor of xx (or xx is NIL below it), and xx lies in the right subtree of bestbest.

Proof. bestbest changes only in line L5, where it becomes the current node and xx becomes its right child. Each later step moves xx to a child of xx, so xx stays in the subtree of that right child. □\square

Termination. Each step moves xx from a node of depth jj to a child of depth j+1j + 1 or to NIL. The depth of a node is at most hh, so after at most h+1h + 1 steps xx is NIL or line L3 has returned.

The answer. If line L3 returns kk, then k=x.key∈D(r)k = x.key \in D(r), and no key at most kk is larger than kk, so f(D(r))=kf(D(r)) = k. If the loop ends, then $x = $ NIL and $f(D(x)) = $ NIL, so by (I) f(D(r))=key(best)f(D(r)) = \mathrm{key}(best), which line L6 returns. The algorithm writes only the local variables xx and bestbest, so TT does not change. ■\blacksquare

Back to BST floor and ceiling