Equivalence classes
From a list of pairs i ≡ j, find the equivalence classes: put each pair on both objects' lists, then gather each class with a stack; O(n + m).
An equivalence relation is reflexive, symmetric and transitive, so a few pairs i ≡ j determine every equivalent
object. Horowitz–Sahni's algorithm has two phases:
- Time:
O(n + m) - Space:
O(n + m) - Horowitz–Sahni, chapter 4
12 objects and 9 pairs; three classes come out: {0, 4, 11, 2, 7}, {1, 3, 5} and {6, 10, 8, 9}.
def equivalence(pairs):# Objects are 0..n-1; each row of pairs says i ≡ j.n = 0for i, j in pairs:n = max(n, i + 1, j + 1)seq = [[] for _ in range(n)]rows = [[x, seq[x], "·"] for x in range(n)] # for the picture: object, partners, classfor i, j in pairs:seq[i].append(j)seq[j].append(i)out = [False] * nclasses = []for i in range(n):if out[i]:continueout[i] = Truecls = [i]rows[i][2] = len(classes)stack = [i]while stack:x = stack.pop()for y in seq[x]:if not out[y]:out[y] = Truecls.append(y)rows[y][2] = len(classes)stack.append(y)classes.append(cls)return classes
Start
The table is still empty.
Local variables
Orange = changed in this step
pairs list
Call stack
- equivalence()line 1
Explanation
The idea
An equivalence relation is reflexive, symmetric and transitive, so a few pairs i ≡ j determine every equivalent
object. Horowitz–Sahni's algorithm has two phases:
- Read the pairs: for each pair put
joni's list andionj's list (symmetry). - Build the classes: start from the first object not yet in a class, push it, and repeatedly pop a member and add all its new partners to the class, pushing them too (transitivity).
This is finding the connected components of a graph with DFS.
Cost
With n objects and m pairs, each pair appears twice on the lists and each appearance is looked at once:
O(n + m).
The input
Each row is a pair [i, j]. The number of objects comes from the largest number.