Algorithm Search⌘K

Proofs: Parent-pointer tree

These are the human proofs of Parent-pointer tree, at full length.

The parent array keeps its invariant, walks up to the root and gives correct child lists full

This proof shows: correctness.

We use the pseudocode of the page and the invariant:

(P1) For each x∈{0,…,n−1}x \in \{0, \ldots, n - 1\}, parent[x]\text{parent}[x] is NIL or an index in {0,…,n−1}\{0, \ldots, n - 1\}. (P2) For each xx, the chain x0=xx_0 = x, xi+1=parent[xi]x_{i+1} = \text{parent}[x_i] reaches NIL: there is a j≤nj \le n with $x_j = $ NIL and x0,…,xj−1x_0, \ldots, x_{j-1} are indices.

The forest. Assume (P1) and (P2). The chain of xx has distinct indices x0,…,xj−1x_0, \ldots, x_{j-1}: if xa=xbx_a = x_b with a<b<ja < b < j, the chain would repeat forever from xax_a and never reach NIL. Hence j≤nj \le n. Let FF be the undirected graph on {0,…,n−1}\{0, \ldots, n - 1\} with the edges {x,parent[x]}\{x, \text{parent}[x]\} for the non-roots xx. Suppose FF had a cycle CC. Give each edge {x,parent[x]}\{x, \text{parent}[x]\} the direction x→parent[x]x \to \text{parent}[x]; each vertex has at most one outgoing edge. A cycle in which each vertex has at most one outgoing edge among its two cycle edges, with ∣C∣|C| vertices and ∣C∣|C| edges, has exactly one outgoing edge at each vertex, so its edges form a directed cycle, and following parents from a vertex of CC goes around CC forever, against (P2). So FF is a forest. Each non-root is joined to the root at the end of its chain, so the components of FF are in bijection with the roots, and FF is a tree when there is exactly one root.

A new tree. The array [NIL][\text{NIL}] with n=1n = 1 meets (P1) and (P2).

ADD-CHILD. Let (P1) and (P2) hold for nn, and let 0≤p<n0 \le p < n. Line L1 sets x=nx = n, and line L2 sets parent[x]=p\text{parent}[x] = p and n′=n+1n' = n + 1. (P1) holds for the old entries, which are unchanged and lie in {0,…,n−1}⊆{0,…,n′ ⁣−1}\{0, \ldots, n - 1\} \subseteq \{0, \ldots, n'\!- 1\}, and for xx, since pp is an index. For (P2), no old entry equals xx, so the chain of an old vertex yy never reaches xx; it is the same chain as before and reaches NIL in at most n≤n′n \le n' steps. The chain of xx is xx followed by the chain of pp, which reaches NIL in at most nn steps, so the chain of xx reaches it in at most n+1=n′n + 1 = n' steps. So (P1) and (P2) hold after the call, and the tree of pp has gained the leaf xx as a child of pp.

PATH-TO-ROOT. Lines L4 to L6 set xx to x0,x1,…x_0, x_1, \ldots and report each xix_i while xi≠x_i \ne NIL. By (P2) the loop stops after j≤nj \le n reports, and it has reported x0,…,xj−1x_0, \ldots, x_{j-1}, the path from xx to the root xj−1x_{j-1} of its tree in FF. Each loop pass costs O(1)O(1), so the time is Θ(j)=Θ(1+depth(x))\Theta(j) = \Theta(1 + \text{depth}(x)), where depth(x)=j−1\text{depth}(x) = j - 1.

CHILD-LISTS. Call xx a child of vv when parent[x]=v\text{parent}[x] = v, and let cvc_v be the number of children of vv. By (P1) each non-root has exactly one parent in {0,…,n−1}\{0, \ldots, n - 1\}.

After line L8: off[0]=0\text{off}[0] = 0 and off[v+1]=cv\text{off}[v + 1] = c_v for 0≤v<n0 \le v < n, since line L8 adds 1 to off[parent[x]+1]\text{off}[\text{parent}[x] + 1] once for each non-root xx, and nothing else changes off\text{off}.

After line L9: off[v]=∑u<vcu\text{off}[v] = \sum_{u < v} c_u for 0≤v≤n0 \le v \le n. The loop keeps the claim "for v′<vv' < v, off[v′]=∑u<v′cu\text{off}[v'] = \sum_{u < v'} c_u, and off[v′′]=cv′′−1\text{off}[v''] = c_{v'' - 1} for v′′≥vv'' \ge v", which holds at v=1v = 1 and is carried from vv to v+1v + 1 by off[v]←cv−1+∑u<v−1cu\text{off}[v] \leftarrow c_{v-1} + \sum_{u < v - 1} c_u. In particular off[n]\text{off}[n] is the number of non-roots, the length of kids\text{kids}, and the intervals Iv=[off[v],off[v+1])I_v = [\text{off}[v], \text{off}[v + 1]) have length cvc_v and partition [0,off[n])[0, \text{off}[n]).

Lines L10 to L14: Line L10 sets next[v]=off[v]\text{next}[v] = \text{off}[v]. We claim that before the pass of line L11 for xx, for each vv, next[v]=off[v]+∣{y<x:parent[y]=v}∣\text{next}[v] = \text{off}[v] + |\{y < x : \text{parent}[y] = v\}|, and the positions off[v],…,next[v]−1\text{off}[v], \ldots, \text{next}[v] - 1 hold the children y<xy < x of vv in increasing order. This holds before x=0x = 0. In the pass for xx, if xx is a root nothing changes and the claim carries over. Otherwise, with v=parent[x]v = \text{parent}[x], line L13 writes xx at position next[v]\text{next}[v], which is less than off[v+1]\text{off}[v + 1] because vv has at least one child ≥x\ge x, namely xx, not yet placed; and line L14 increments next[v]\text{next}[v]. The written position lies in IvI_v, so no other block is touched, and xx follows the smaller children of vv. So the claim holds for x+1x + 1.

After the pass for x=n−1x = n - 1, each IvI_v holds exactly the cvc_v children of vv in increasing order, so kids[off[v] .. off[v+1]−1]\text{kids}[\text{off}[v] \,..\, \text{off}[v + 1] - 1] is the list of children of vv, as line L15 returns. Lines L7, L8, L9 and L10 each take Θ(n)\Theta(n) time, and lines L11 to L14 take Θ(n)\Theta(n), so CHILD-LISTS runs in Θ(n)\Theta(n) time with 2n+12n + 1 words of output. □\square

Back to Parent-pointer tree