Proofs: Persistent union-find
These are the human proofs of Persistent union-find, at full length.
The persistent union-find gives every version its own partition full
This proof shows: correctness.
Setting. Each persistent array version has fixed contents in the abstract model: the first version holds its initial list, and SET returns a version whose contents are with slot set to . A handle denotes the partition whose blocks are the trees of the forest .
Lemma 1 (rerooting). Let the diffs and the shared array of one family satisfy: the Arr version has , and each version has with slot set to . Then after REROOT the same holds, is the Arr version, and no version has changed contents.
Proof. We also keep the fact that the Diff pointers form a tree whose root is the Arr version; it holds for a single version, SET puts a new root above the old one, and each step below reverses one edge of the path to the root, which keeps a tree with the new root. So the path of line A1 ends at the Arr version. Consider the step for (lines A3 to A6). Before it, is Arr with , and . After it, has slot set to , which is , and is Arr. The version becomes with the old ; since and differ at most in slot , the contents of equal with slot set to . No other version changes. So the hypothesis holds after each step, and at the end is Arr.
Corollary 2. GET returns slot of , and SET returns a version with the contents of the model, while no existing version changes contents.
Proof. GET reroots and reads . SET reroots, makes the Arr version, writes into so that , and turns into with the old value, which keeps the hypothesis of Lemma 1. (When equals the old value, the implementation returns itself, which has the right contents.)
By Corollary 2 we can reason about each handle as if its arrays were plain arrays with the contents , which no later operation changes, except where a line writes to a handle field.
Invariant. A handle is good if its forest and ranks satisfy: the roots of are its fixed points, and for each non-root .
Lemma 3 (find). On a good handle, FIND returns the root of the tree of in , and afterwards is good and is unchanged.
Proof. The walk of line L2 visits nodes of strictly growing rank, so it ends, at a fixed point , which is the root of the tree of . The loop of line L4 visits the same nodes and sets the parent of each with to . Each stays in the tree of , every other node keeps its parent, and the subtrees below each move with it, so the trees, and hence the blocks, are the same. The ranks along the walk grow strictly, so for , and the invariant holds for the new parent of each . Line L5 stores the new parent array, so is good and is unchanged.
Lemma 4 (union). On a good handle, UNION returns a good handle with equal to with the blocks of and merged, and is unchanged.
Proof. By Lemma 3, line L7 gives the roots and and leaves as it was. If they are equal (line L8), and share a block and is the answer. Otherwise lines L10 to L12 set the parent of one root to the other root, which joins the two trees into one and leaves all other trees alone, so the blocks of and are merged. If the ranks differ, the new child has the smaller rank, so the invariant holds. If they are equal, the new parent's rank goes up by one, which keeps the invariant at the new child and at the old children of . All writes are SET calls, which make new versions by Corollary 2, so keeps its contents.
Theorem. For every sequence of operations, every handle is good and denotes the partition that the abstract type gives to it, and FIND and SAME return answers that agree with that partition, on every version.
Proof. MAKE gives the forest of roots with rank 0, which is good and denotes the singletons. By Lemmas 3 and 4, by induction on the number of operations, every handle is good and denotes the right partition. The only write to an existing handle is line L5, which by Lemma 3 does not change the partition, so no later answer on any version changes. FIND returns a root of the tree of ; two elements share a root exactly when they share a tree, so SAME is correct.