MIT 6.1220 — Lecture 3

DSU and Amortized Analysis

Problem. (Disjoint Set) Consider a collection of pairwise disjoint sets DS:={S1,S2,…,Sr}\text{DS} := \{ S_1, S_2, \dots, S_r\}, where each set SiS_i contains a single representative Rep[Si]∈Si\textsc{Rep}[S_i] \in S_i. Suppose DS\text{DS} supports the following operations:

This data structure is called the Union Find data structure.

The idea is to say each set is a doubly-linked list. For example,

DS={{3,9,4}, {2,7,1,8}}   ⟹   {3↔9↔4} and {2↔7↔1↔8}.\text{DS} = \{ \{3, 9, 4\}, \ \{2, 7, 1, 8\} \} ~ \implies ~ \{ 3 \leftrightarrow 9 \leftrightarrow 4 \} \text{ and } \{ 2 \leftrightarrow 7 \leftrightarrow 1 \leftrightarrow 8 \}.

So each element in each SiS_i has a prev and next pointer. And we'll say Rep[Si]\textsc{Rep}[S_i] is the head of SiS_i.

Solution. (Disjoint Set #1) If every SiS_i is a doubly-linked list, then:

The time complexity achieved here is:

Make-Set:Θ(1)      Find-Set(x):Θ(∣S(x)∣)      Union(x,y):Θ(∣S(x)∣+∣S(y)∣).\textsc{Make-Set}: \Theta(1) ~~~~~~ \textsc{Find-Set}(x): \Theta(|S(x)|) ~~~~~~ \textsc{Union}(x, y): \Theta(|S(x)| + |S(y)|).

But this is a little silly. It shouldn't take Θ(∣S(x)∣)\Theta(|S(x)|) just to find the head; S(x)S(x) can just store the head in metadata.

Solution. (Disjoint Set #2) Every element now furthermore points to a metadata object pointing to the head.

But we can do even better by considering an amortized analysis.

Definition (Amortized Cost). A data structure D\mathbf{D} has an amortized cost of TT if any sequence of kk operations starting from the initial state of D\mathbf{D} has a total cost at most k⋅Tk \cdot T.

Solution. (Disjoint Set #3) Let's be smarter about handling Union(x,y)\textsc{Union}(x, y): always merge so that ∣S(y)∣≤∣S(x)∣|S(y)| \leq |S(x)|. We claim this yields an amortized cost of Θ(log⁡n)\Theta(\log n), where n:=∑i=1r∣Si∣n := \sum_{i = 1}^r |S_i|.

Proof: Any individual element has its pointer updated at most O(log⁡n)O(\log n) times, so Union steps take at most O(nlog⁡n)O(n \log n) time. Every other step takes Θ(1)\Theta(1) time. And k≥nk \geq n.   ■\blacksquare

More generally, there are three strategies for computing amortized cost.

Solution. (Disjoint Set #4) Every set is now a rooted tree.

Obviously this is pretty bad. But let's be smarter.

Solution. (Disjoint Set #5) In Union\textsc{Union}, point roots from the shorter tree to the taller tree (by tree height).

Great! We have a second solution that yields O(log⁡n)O(\log n) amortized cost. Let's get one more third solution:

Solution. (Disjoint Set #6) Any time we perform “traversal to the root”, compress the tree!

(u1→u2→⋯→um→R)   ⟹   (u1→R,  u2→R,  …,  um→R)(u_1 \to u_2 \to \dots \to u_m \to R) ~ \implies ~ (u_1 \to R, ~~ u_2 \to R, ~~ \dots, ~~ u_m \to R)

We claim this also yields an amortized cost of O(log⁡n)O(\log n).

Proof: Define the potential by Φ:=∑x∈Vlog⁡(L(x))\Phi := \sum_{x \in V} \log (L(x)), where L(x)L(x) denotes the size of the subtree rooted at xx.

And so this solution also has O(log⁡n)O(\log n) amortized cost overall.   ■\blacksquare

It turns out that if you combine the approaches of Disjoint Set #5 and Disjoint Set #6, you get a solution with O(α(n))O(\alpha(n)) amortized cost, where α\alpha is the Inverse-Ackermann Function. For most reasonable nn, we have α(n)≤4\alpha(n) \leq 4.