素集合データ構造 (Union-Find data structure, Disjoint-set data structure)
概要
Union-Findでは以下の操作を行うことができる
-
\(union(u, v)\) : 要素uの属するグループと要素vが属するグループを1つにまとめる
-
\(find(u)\) : 要素uが属するグループを求める
\(find(u) = find(v)\) であればuとvは同じグループに属することが分かる
計算量
以下を行うことにより \(O(\alpha(n))\) になる。 (\(\alpha\) はアッカーマン関数の逆関数)
-
rankもしくはsizeによるマージ先の選択
-
経路圧縮
片方のみだと \(O(\log n)\) になる。
実装 (Union by rank)
rankによってマージ先を選択する実装
N = 10**5
*p, = range(N)
rank = [1]*N
def root(x):
if x == p[x]:
return x
p[x] = y = root(p[x])
return y
def unite(x, y):
px = root(x); py = root(y)
if px == py:
return 0
rx = rank[px]; ry = rank[py]
if ry < rx:
p[py] = px
elif rx < ry:
p[px] = py
else:
p[py] = px
rank[px] += 1
return 1
実装 (Union by size)
size(集合のサイズ)によってマージ先を選択する実装
N = 10**5
*p, = range(N)
size = [1]*N
def root(x):
if x == p[x]:
return x
p[x] = y = root(p[x])
return y
def unite(x, y):
px = root(x); py = root(y)
if px == py:
return 0
sx = size[px]; sy = size[py]
if sy < sx:
p[py] = px
size[px] += sy
else:
p[px] = py
size[py] += sx
return 1
実装 (Linking by index)
シンプルなUnion-Find実装。
インデックスの若い方を親にしてマージする実装
計算量的には "by size" と同じになることが示せる。しかし、実際の処理では少しだけ遅くなる(rootの呼ばれる回数が多くなる)。
シンプルな実装であるため、Pythonではちょっと高速になることが多い。(PyPyではby rankの方が高速)
# Union-Find data structure
N = 10**5
*p, = range(N)
def root(x):
if x == p[x]:
return x
p[x] = y = root(p[x])
return y
def unite(x, y):
px = root(x); py = root(y)
if px == py:
return 0
if px < py:
p[py] = px
else:
p[px] = py
return 1