Algorithm Search⌘K

Proofs: Fully persistent array

These are the human proofs of Fully persistent array, at full length.

The path-copying array returns the right value in every version and uses O(log n) time and space per write full

This proof shows: correctness, termination, complexity.

We use the definitions of the page, with n≥1n \ge 1. For integers lo<hilo < hi, a tree for [lo,hi)[lo, hi) is a leaf if hi−lo=1hi - lo = 1, and otherwise a node whose left child is a tree for [lo,mid)[lo, mid) and whose right child is a tree for [mid,hi)[mid, hi), with mid=⌊(lo+hi)/2⌋mid = \lfloor (lo + hi)/2 \rfloor. Since hi−lo≥2hi - lo \ge 2 gives lo<mid<hilo < mid < hi, both ranges are non-empty and shorter than [lo,hi)[lo, hi). For a tree vv for [lo,hi)[lo, hi) and an index i∈[lo,hi)i \in [lo, hi), let leaf(v,i)\text{leaf}(v, i) be the leaf reached from vv by going to the left child when i<midi < mid and to the right child otherwise, and let val(v,i)\text{val}(v, i) be its value. The invariant of the page says that val(roots[t],i)=At[i]\text{val}(\text{roots}[t], i) = A_t[i] for every version tt and index i∈[0,n)i \in [0, n).

Lemma 1 (BUILD). For lo<hilo < hi, BUILD(a,lo,hi)(a, lo, hi) returns a tree for [lo,hi)[lo, hi) with val(v,i)=a[i]\text{val}(v, i) = a[i] for every i∈[lo,hi)i \in [lo, hi), made of 2(hi−lo)−12(hi - lo) - 1 new nodes.

Proof. By induction on hi−lohi - lo. If it is 1, line L1 returns a leaf for [lo,lo+1)[lo, lo + 1) that holds a[lo]a[lo]. Otherwise, by the hypothesis, line L3 returns a node whose children are trees for [lo,mid)[lo, mid) and [mid,hi)[mid, hi) with the right values. For i<midi < mid, leaf(v,i)\text{leaf}(v, i) is the leaf of ii in the left child, which holds a[i]a[i], and likewise for i≥midi \ge mid on the right. The number of nodes is 1+(2(mid−lo)−1)+(2(hi−mid)−1)=2(hi−lo)−11 + (2(mid - lo) - 1) + (2(hi - mid) - 1) = 2(hi - lo) - 1. □\square

Lemma 2 (GET). If vv is a tree for [0,n)[0, n) and 0≤i<n0 \le i < n, GET(v,i)(v, i) returns val(v,i)\text{val}(v, i) after at most ⌈lg⁡n⌉\lceil \lg n \rceil iterations.

Proof. At the start of each iteration of line L5, vv is the node for [lo,hi)[lo, hi) on the path to leaf(v0,i)\text{leaf}(v_0, i) from the root v0v_0, and lo≤i<hilo \le i < hi. This holds at line L4. If hi−lo>1hi - lo > 1, lines L6 and L7 move to the child whose range contains ii, which is the next node on the path by the definition of leaf, and keep lo≤i<hilo \le i < hi. Each iteration replaces hi−lohi - lo by ⌊(hi−lo)/2⌋\lfloor (hi - lo)/2 \rfloor or ⌈(hi−lo)/2⌉\lceil (hi - lo)/2 \rceil, so after jj iterations hi−lo≤⌈n/2j⌉hi - lo \le \lceil n / 2^j \rceil, and the loop stops after at most ⌈lg⁡n⌉\lceil \lg n \rceil iterations with hi−lo=1hi - lo = 1. Then vv is leaf(v0,i)\text{leaf}(v_0, i), and line L8 returns its value. □\square

Lemma 3 (SET). If vv is a tree for [lo,hi)[lo, hi) and lo≤i<hilo \le i < hi, then SET(v,lo,hi,i,x)(v, lo, hi, i, x) returns a tree v′v' for [lo,hi)[lo, hi) such that val(v′,i)=x\text{val}(v', i) = x and leaf(v′,j)=leaf(v,j)\text{leaf}(v', j) = \text{leaf}(v, j) for every j∈[lo,hi)j \in [lo, hi) with j≠ij \ne i. It changes no existing node and builds one new node for each node on the path from vv to leaf(v,i)\text{leaf}(v, i), which is at most ⌈lg⁡(hi−lo)⌉+1\lceil \lg(hi - lo) \rceil + 1 nodes.

Proof. By induction on hi−lohi - lo. If it is 1, then j≠ij \ne i is impossible in [lo,hi)[lo, hi), and line L9 returns a new leaf holding xx. Otherwise suppose i<midi < mid; the case i≥midi \ge mid is symmetric with line L12. By the hypothesis applied to the left child, which is a tree for [lo,mid)[lo, mid) that contains ii, the call returns a tree ℓ′\ell' for [lo,mid)[lo, mid) with val(ℓ′,i)=x\text{val}(\ell', i) = x that agrees with the old left child on every other leaf. Line L11 returns a new node with children ℓ′\ell' and the old right child, which is a tree for [lo,hi)[lo, hi). For j=ij = i or j<midj < mid, the path goes left and the claim follows from the hypothesis. For j≥midj \ge mid, the path goes right, into the old right child, so leaf(v′,j)=leaf(v,j)\text{leaf}(v', j) = \text{leaf}(v, j). Lines L9, L11 and L12 only build new nodes and read fields of old ones, so no existing node changes. The count of new nodes is one per level of the path, and the path has at most ⌈lg⁡(hi−lo)⌉+1\lceil \lg(hi - lo) \rceil + 1 nodes by the halving argument of Lemma 2. □\square

Theorem. After any sequence of UPDATE calls on any existing versions, the invariant holds: GET(roots[t],i)=At[i]\text{GET}(\text{roots}[t], i) = A_t[i] for every version tt and every i∈[0,n)i \in [0, n). GET takes O(log⁡n)O(\log n) time, each UPDATE takes O(log⁡n)O(\log n) time and adds at most ⌈lg⁡n⌉+1\lceil \lg n \rceil + 1 nodes, and after mm versions the structure holds at most 2n−1+(m−1)(⌈lg⁡n⌉+1)2n - 1 + (m - 1)(\lceil \lg n \rceil + 1) nodes.

Proof. By induction on the number of updates. Version 0 satisfies the invariant by Lemma 1. Suppose it holds for versions 0,…,m−10, \ldots, m - 1, and UPDATE(t,i,x)(t, i, x) creates version mm, defined by Am[i]=xA_m[i] = x and Am[j]=At[j]A_m[j] = A_t[j] for j≠ij \ne i. By Lemma 3, the new root v′v' of line L13 satisfies val(v′,i)=x=Am[i]\text{val}(v', i) = x = A_m[i] and val(v′,j)=val(roots[t],j)=At[j]=Am[j]\text{val}(v', j) = \text{val}(\text{roots}[t], j) = A_t[j] = A_m[j] for j≠ij \ne i. No existing node changed, so for every older version ss, the leaves reached from roots[s]\text{roots}[s] hold the same values as before, and the invariant still holds for ss. Lemma 2 then gives the claim about GET. The time and the node counts follow from Lemmas 1 to 3, since every line does O(1)O(1) work besides the recursive calls. ■\blacksquare

Back to Fully persistent array