Algorithm Search⌘K

Proofs: k-d tree

These are the human proofs of k-d tree, at full length.

The k-d tree operations keep the order invariant and answer range and nearest queries exactly full

This proof shows: correctness, termination.

We use the names of the pseudocode on the page. For an axis aa, the order <a<_a compares the superkeys (pa,pa+1,…,pa−1)(p_a, p_{a+1}, \ldots, p_{a-1}) lexicographically. It is a total order on Rk\mathbb{R}^k, and p<aqp <_a q implies pa≤qap_a \le q_a. Write S(t)S(t) for the multiset of points in the subtree of tt.

Invariant. (A) A node at depth dd has axis d mod kd \bmod k. (K) At each node tt with axis aa, each p∈S(t.left)p \in S(t.left) has p<at.pointp <_a t.point, and each p∈S(t.right)p \in S(t.right) has p≥at.pointp \ge_a t.point.

Lemma 1 (region). Let (K) hold. For each node uu, define R(root)=RkR(\text{root}) = \mathbb{R}^k, R(t.left)=R(t)∩{x:xa≤t.pointa}R(t.left) = R(t) \cap \{x : x_a \le t.point_a\} and R(t.right)=R(t)∩{x:xa≥t.pointa}R(t.right) = R(t) \cap \{x : x_a \ge t.point_a\}, where a=t.axisa = t.axis. Then S(u)⊆R(u)S(u) \subseteq R(u).

Proof. By induction on depth. The root is clear. A point pp of S(t.left)S(t.left) is in S(t)⊆R(t)S(t) \subseteq R(t) and has p<at.pointp <_a t.point, so pa≤t.pointap_a \le t.point_a; the right side is the same with ≥\ge. □\square

BUILD. By induction on ∣P∣|P|, BUILD(P,d)(P, d) returns a tree on exactly the multiset PP in which (A) and (K) hold with depths counted from dd. For P=∅P = \emptyset, line L1 returns the empty tree. Otherwise line L3 picks m∈Pm \in P. The set of line L5 holds the points p<amp <_a m, and the multiset of line L6 holds the points p≥amp \ge_a m with one copy of mm removed; together with mm these are exactly PP, and both are smaller than PP, so the induction applies and the recursion stops. The node gets axis d mod kd \bmod k and its children get depth d+1d + 1, so (A) holds. Each point of the left subtree is <am<_a m and each point of the right subtree is ≥am\ge_a m, so (K) holds at the new node, and below it by induction.

INSERT. Each pass of the loop of lines L14 to L16 moves tt one level down, so the loop stops after at most hh passes. The new node is a child of the last tt, with axis one more than the axis of tt modulo kk, so (A) holds. At each node uu on the path, the loop went left exactly when p<u.axisu.pointp <_{u.axis} u.point, so the new point is on the side of uu that (K) asks for. Nodes off the path do not change.

FIND-MIN. FIND-MIN(u,a)(u, a) returns a node whose point is least in <a<_a over S(u)S(u). By induction on the size: the candidates are uu and the minima of the subtrees it visits. The only subtree it skips is u.rightu.right when u.axis=au.axis = a, and then by (K) each point there is ≥au.point\ge_a u.point, so u.pointu.point is at least as small. It stops since each call goes to a smaller subtree.

DELETE. If line L23 finds no node, nothing changes. Suppose the search reaches a node t0t_0 with point pp. Each pass of lines L24 and L25 replaces t.pointt.point by the point of a node mm strictly below tt, and then works on mm; line L26 removes a leaf. The depth of tt grows at each pass, so the loop stops after at most hh passes, and the multiset of points loses exactly one copy of pp: the point of t0t_0 is overwritten, each copied point leaves its old node at the next pass, and the last node is removed. (A) holds, since no node changes depth except in line L25, treated below.

We show (K) holds after each pass, at every node. Fix a pass at node tt with axis aa, and let mm be the node that FIND-MIN returns, with point μ\mu.

  • Line L24. μ\mu is least in <a<_a over S(t.right)S(t.right), so every other point that stays in the right subtree is ≥aμ\ge_a \mu. Each point of the left subtree is <a<_a the old point of tt, which is ≤aμ\le_a \mu since μ\mu came from the right subtree, so it is <aμ<_a \mu. So (K) holds at tt. For an ancestor uu of tt, μ\mu moved from S(t)S(t) to tt, which are on the same side of uu, so the constraint of uu on μ\mu is unchanged. The node mm still holds μ\mu for now, and the next pass treats it.
  • Line L25. Here t.rightt.right is empty. μ\mu is least in <a<_a over S(t.left)S(t.left), so every point that stays in that subtree is ≥aμ\ge_a \mu. The subtree moves to the right side of tt, at the same depth, so (A) holds, and (K) at tt asks exactly for ≥aμ\ge_a \mu. Inside the moved subtree nothing changed. Ancestors are as in line L24.
  • Line L26. Removing a leaf keeps (K) everywhere.

During the passes the point μ\mu is held by two nodes, tt and mm; the next pass overwrites the point of mm, so at the end each point is held once and (K) holds at every node.

RANGE-SEARCH. Let B=∏i[ℓi,hi]B = \prod_i [\ell_i, h_i]. Every point reported is in BB by line L9, and each node is visited at most once, since the calls follow the tree, so no point is reported twice from one node. Suppose p∈Bp \in B is the point of a node vv, and let tt be an ancestor of vv with axis aa. If vv is in the left subtree of tt, then by Lemma 1 pa≤t.pointap_a \le t.point_a, and ℓa≤pa\ell_a \le p_a, so the test of line L11 holds; if it is in the right subtree, t.pointa≤pa≤hat.point_a \le p_a \le h_a, and the test of line L12 holds. So the search reaches vv, and line L9 reports pp. The search visits at most nn nodes and stops.

NEAREST. Let qq be the query and d∗d^* the value of the global bound. We show that at the end bestbest is a point of the tree at the least distance from qq. The value d∗d^* only goes down and is always the distance from qq to bestbest. Suppose a point p∘p^\circ at the least distance ρ\rho is never compared in line L18. Then some call skipped a subtree that holds it: line L22 at a node tt with axis aa decided not to search farfar, with ∣δ∣≥d∗|\delta| \ge d^*. The subtree farfar is on the other side of xa=t.pointax_a = t.point_a from qq, or on that plane, so by Lemma 1, ∣qa−pa∘∣≥∣δ∣|q_a - p^\circ_a| \ge |\delta|, and ρ=dist⁡(q,p∘)≥∣δ∣≥d∗\rho = \operatorname{dist}(q, p^\circ) \ge |\delta| \ge d^*. Since d∗d^* is the distance of a point of the tree, d∗≥ρd^* \ge \rho, so d∗=ρd^* = \rho, and bestbest is already a nearest point. If no skip hides p∘p^\circ, line L18 compares it and d∗≤ρd^* \le \rho afterward. In both cases the final bestbest is at distance ρ\rho. The calls follow the tree, so they stop. ■\blacksquare

Back to k-d tree