Algorithm Search⌘K

Proofs: Tarjan's offline LCA algorithm

These are the human proofs of Tarjan's offline LCA algorithm, at full length.

Tarjan's offline LCA algorithm answers every query correctly full

This proof shows: correctness, termination.

Let TT be a tree with the vertices 0,…,n−10, \ldots, n-1, rooted at rr, and let p(v)p(v) be the parent of v≠rv \ne r. Write TvT_v for the subtree of vv, and u⪯vu \preceq v when uu is an ancestor of vv (each vertex is an ancestor of itself). The algorithm is TARJAN-OFFLINE-LCA with the lines L1 to L11.

Lemma 1 (the search). The stack SS always holds the path from rr to its top vertex, with the parent of each entry below it. Each vertex is pushed exactly once and popped exactly once, and a vertex vv is popped at line L8 only after every vertex of Tv∖{v}T_v \setminus \{v\} has been popped.

Proof. Line L6 pushes a child cc of the top vv, so the path property holds after a push, and a pop removes the last vertex of the path. A vertex c≠rc \ne r is pushed only from its parent, at most once per edge {p(c),c}\{p(c), c\}, because each neighbour of vv is taken once. The vertex vv is popped only when it has no neighbour left to take, that is, after each child cc was pushed and, since cc lies above vv on the stack, popped again. By induction on the height of vv, every vertex of TcT_c was popped before cc. Each vertex is pushed: by induction on the depth, a child cc of vv is taken before vv is popped. □\square

Termination. Each iteration of line L4 either pushes a vertex or pops one. By Lemma 1 there are nn pushes and nn pops, so the loop stops after 2n2n iterations.

Call a vertex open when it is on SS, and done when it has been popped. For a done vertex ww, let a(w)a(w) be the lowest open ancestor of ww, if one exists.

Lemma 2 (the invariant). At the start of each iteration of line L4, and also during line L9 for a vertex vv, the following hold.

(a) For each open vertex xx, the set of xx is {x}\{x\} together with the subtrees TcT_c of the children cc of xx that are done.

(b) For each done vertex ww with an open ancestor, the set of ww is the set of a(w)a(w), and its label is a(w)a(w).

During line L9 for vv, the vertex vv is done but not yet merged, and (a) holds for vv as if it were open: the set of vv is TvT_v, with the label vv.

Proof. At the start every set is a single vertex with its own label, and no vertex is done, so (a) and (b) hold. A push of cc changes no set, and cc has no done child yet, so its set {c}\{c\} fits (a).

Consider the pop of vv with parent pp. Just before it, by Lemma 1 all children of vv are done, so by (a) the set of vv is TvT_v, and its label is vv: by (b) for a done w∈Tvw \in T_v, a(w)=va(w) = v because vv is open and every vertex between ww and vv is done. This is the claim during line L9.

At line L10, UNION(p,v)(p, v) joins the set of pp, which by (a) is {p}\{p\} and the subtrees of the done children of pp, with TvT_v. The child vv is now done, so the new set is what (a) asks for pp. The label of the new set becomes pp. For each w∈Tvw \in T_v, now done, the lowest open ancestor is pp, since vv is no longer open and pp is. For each done ww in an older subtree of a child of pp, a(w)=pa(w) = p is unchanged. All other sets are unchanged, and the open status of no other vertex changes. So (a) and (b) hold after the pop. If v=rv = r, line L10 does nothing and no open vertex is left. □\square

Lemma 3 (the answer). Let line L9 run for vv, and let ww be done. Then anc[FIND(w)]=lca(v,w)\mathit{anc}[\mathrm{FIND}(w)] = \mathrm{lca}(v, w).

Proof. At this moment the open vertices are the proper ancestors of vv, and vv itself counts as open for its set, as Lemma 2 says. If w∈Tvw \in T_v, then lca(v,w)=v\mathrm{lca}(v, w) = v, and the set of ww is TvT_v with the label vv.

Otherwise w∉Tvw \notin T_v. The root is an ancestor of ww and is open, so a(w)a(w) exists, and by Lemma 2(b) the label of the set of ww is a(w)a(w), the lowest vertex of the path from rr to p(v)p(v) that is an ancestor of ww. Every vertex of this path is an ancestor of vv. So a(w)a(w) is a common ancestor of vv and ww. Every common ancestor of vv and ww that is not vv lies on the path from rr to p(v)p(v), and vv is not a common ancestor because w∉Tvw \notin T_v. So a(w)a(w) is the lowest common ancestor. □\square

Theorem. TARJAN-OFFLINE-LCA stops, and for each query i=(u,v)i = (u, v) it returns lca(u,v)\mathrm{lca}(u, v) in ans[i]\mathit{ans}[i].

Proof. It stops by the termination argument. By line L2, the query ii is stored at uu and at vv. Each vertex is popped once (Lemma 1). If u=vu = v, then when vv is popped, line L8 marks vv done before line L9, and Lemma 3 with w=vw = v gives anc[FIND(v)]=v=lca(v,v)\mathit{anc}[\mathrm{FIND}(v)] = v = \mathrm{lca}(v, v). If u≠vu \ne v, let vv be the one popped second. At the pop of uu, vv is not done, so line L9 writes nothing for ii there; at the pop of vv, uu is done, and Lemma 3 gives ans[i]=lca(v,u)\mathit{ans}[i] = \mathrm{lca}(v, u). No later step writes ans[i]\mathit{ans}[i], since both entries of the query are read only at these two pops. ■\blacksquare

Back to Tarjan's offline LCA algorithm