Algorithm Search⌘K

Proofs: Linked-list disjoint sets

These are the human proofs of Linked-list disjoint sets, at full length.

With weighted union, m operations on n elements take O(m + n log n) time full

This proof shows: time.amortized, time.total.

Consider a sequence of mm operations MAKE-SET, FIND and UNION, of which nn are MAKE-SET, on linked-list disjoint sets with the weighted-union rule of line L6. We prove that the sequence takes O(m+nlog⁡n)O(m + n \log n) time.

Cost of each operation. MAKE-SET and FIND do a constant number of steps. A UNION with S=TS = T does a constant number of steps. A UNION with S≠TS \ne T does a constant number of steps outside line L7, and line L7 does one step for each element of the list of the smaller set SS, each of which gets a new value of set. So the total time is O(m+M)O(m + M), where MM is the total number of times that some element gets a new value of set at line L7.

Each element moves at most ⌊lg⁡n⌋\lfloor \lg n \rfloor times. Fix an element xx. Let σ(x)\sigma(x) be the size of the set of xx; it never falls, because sets only merge. Suppose xx moves at line L7 of some union. Then xx is in the set SS after line L6, so ∣S∣≤∣T∣|S| \le |T|, and after the union xx is in a set of size ∣S∣+∣T∣≥2∣S∣|S| + |T| \ge 2|S|. So each move of xx at least doubles σ(x)\sigma(x). Before its first move σ(x)≥1\sigma(x) \ge 1, so after kk moves σ(x)≥2k\sigma(x) \ge 2^k. A set has at most nn elements, so 2k≤n2^k \le n, and k≤⌊lg⁡n⌋k \le \lfloor \lg n \rfloor.

Sum. Each of the nn elements moves at most ⌊lg⁡n⌋\lfloor \lg n \rfloor times, so M≤n⌊lg⁡n⌋M \le n \lfloor \lg n \rfloor, and the total time is O(m+nlog⁡n)O(m + n \log n). Since m≥nm \ge n, this is at most O(log⁡n)O(\log n) per operation on average, which gives the amortized bound: with the accounting method, charge 1+⌊lg⁡n⌋1 + \lfloor \lg n \rfloor to each MAKE-SET and 11 to each other operation; each move at line L7 is paid by one unit of the element that moves, and no element runs out. ■\blacksquare

The bound is tight. Let n=2kn = 2^k, and do the unions in kk rounds: in round jj, pair up the 2k−j+12^{k-j+1} sets of size 2j−12^{j-1} and merge each pair. Each union of two sets of size 2j−12^{j-1} moves 2j−12^{j-1} elements, and there are 2k−j2^{k-j} such unions, so each round moves n/2n/2 elements, and the kk rounds move (n/2)lg⁡n(n/2) \lg n. With m=2n−1m = 2n - 1 operations, the total is Θ(m+nlog⁡n)\Theta(m + n \log n).

Linked-list disjoint sets keep one list per set and answer each query correctly full

This proof shows: correctness, termination.

We write EE for the set of elements made so far, and for each set object SS we write L(S)L(S) for the sequence S.head,S.head.next,…S.head, S.head.next, \ldots, followed until NIL. We prove that after each operation the invariant of the page holds:

(L) For each set object SS, the sequence L(S)L(S) has exactly S.sizeS.size distinct elements, ends at S.tailS.tail with S.tail.next=NILS.tail.next = \mathrm{NIL}, and each of its elements xx has x.set=Sx.set = S. Each element of EE is in exactly one sequence L(S)L(S).

Under (L), the sets of the structure are the sets of elements of the lists L(S)L(S), they are disjoint and they cover EE, and the representative of the set of xx is x.set.headx.set.head.

Initialization. With no elements and no set objects, (L) holds trivially.

MAKE-SET(x)(x), for a new element xx. Lines L1 and L2 make an object SS with L(S)=(x)L(S) = (x), S.size=1S.size = 1, S.tail=xS.tail = x, x.next=NILx.next = \mathrm{NIL} and x.set=Sx.set = S. No other field changes, and xx was in no list before. So (L) holds, and the new set is {x}\{x\}, as the disjoint-set type requires.

FIND(x)(x). Line L3 changes nothing and returns x.set.headx.set.head. By (L), xx is in L(x.set)L(x.set), and two elements get the same answer exactly when they have the same set object, that is, when they are in one set. It stops after two reads.

UNION(x,y)(x, y). Assume (L). Line L4 sets S=x.setS = x.set and T=y.setT = y.set, the objects of the sets of xx and yy. If S=TS = T, the two elements are in one set, line L5 returns, and nothing changes.

Let S≠TS \ne T. Line L6 may swap the names SS and TT; the rest of the proof uses the names after the swap, and the claim to prove is symmetric in the two sets. Let L(S)=(s1,…,sa)L(S) = (s_1, \ldots, s_a) and L(T)=(t1,…,tb)L(T) = (t_1, \ldots, t_b), with a=S.sizea = S.size and b=T.sizeb = T.size. These a+ba + b elements are distinct, because each element is in only one list.

Line L7. The walk visits s1,…,sas_1, \ldots, s_a and sets si.set←Ts_i.set \leftarrow T. It stops after aa steps, because sa.next=NILs_a.next = \mathrm{NIL} by (L), and it changes no next field, so the list it walks does not change during the walk.

Line L8. Before it, tb=T.tailt_b = T.tail and tb.next=NILt_b.next = \mathrm{NIL}. It sets tb.next←s1t_b.next \leftarrow s_1 and T.tail←saT.tail \leftarrow s_a. Now L(T)=(t1,…,tb,s1,…,sa)L(T) = (t_1, \ldots, t_b, s_1, \ldots, s_a): from t1t_1 the old links lead to tbt_b, the new link leads to s1s_1, and the old links of SS lead to sas_a, whose next is NIL.

Line L9. It sets T.size←a+bT.size \leftarrow a + b and removes SS.

Now check (L). The list L(T)L(T) has a+ba + b distinct elements, equal to T.sizeT.size; it ends at T.tail=saT.tail = s_a with sa.next=NILs_a.next = \mathrm{NIL}; its first bb elements had set=Tset = T and kept it, and the last aa got set=Tset = T at line L7. The object SS no longer exists, and no element points to it. Every other object and its list is unchanged, because lines L7 to L9 change only fields of s1,…,sas_1, \ldots, s_a, tbt_b, SS and TT. Each element is still in exactly one list: the elements of the old lists L(S)L(S) and L(T)L(T) are now in the new L(T)L(T), and the other lists did not change. So (L) holds. The new set of TT is the union of the two old sets, and all other sets are as before, which is the effect that the disjoint-set type requires of UNION. The swap of line L6 changes only which of the two objects survives, so the sets are the same with or without it.

Conclusion. By induction on the number of operations, (L) holds after each operation, each operation stops, and FIND returns a representative that is shared by exactly the members of one set. ■\blacksquare

Back to Linked-list disjoint sets