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}}      {394} and {2718}.\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 kTk \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 Θ(logn)\Theta(\log n), where n:=i=1rSin := \sum_{i = 1}^r |S_i|.

Proof: Any individual element has its pointer updated at most O(logn)O(\log n) times, so Union steps take at most O(nlogn)O(n \log n) time. Every other step takes Θ(1)\Theta(1) time. And knk \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(logn)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!

(u1u2umR)      (u1R,  u2R,  ,  umR)(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(logn)O(\log n).

Proof: Define the potential by Φ:=xVlog(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(logn)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.