출시·고도화 중
유니온 파인드 안내서 · 6/6
유니온 파인드는 코딩 테스트용 자료 구조에 그치지 않습니다. 그래프 라이브러리, 이미지 처리, 컴파일러, 데이터 정제 파이프라인 곳곳에 들어 있습니다. 이 장에서는 실제 라이브러리에서 쓰는 방법과 자주 하는 실수, 더 읽을 자료를 정리합니다.
직접 구현하기 전에 이미 쓰고 있는 라이브러리에 있는지 확인하세요.
scipy.cluster.hierarchy.DisjointSet(SciPy 1.6부터)은 정수뿐 아니라 해시 가능한 어떤 객체도 원소로 받습니다.networkx.utils.UnionFind를 내부의 크루스칼 최소 신장 트리 구현 등에 사용합니다.boost::disjoint_sets를 제공하며, 연결 요소 증분 계산에 씁니다.label이나 OpenCV의 connectedComponents 같은 연결 영역 라벨링은 두 번 훑는(two-pass) 방식에서 임시 라벨을 합칠 때 유니온 파인드와 같은 원리를 씁니다.from scipy.cluster.hierarchy import DisjointSet
ds = DisjointSet(["a", "b", "c", "d"])
ds.merge("a", "b")
ds.merge("c", "d")
print(ds.connected("a", "c")) # False
ds.add("e")
ds.merge("b", "e")
print(sorted(map(sorted, ds.subsets()))) # [['a', 'b', 'e'], ['c', 'd']]실무 데이터의 키는 대개 정수가 아닙니다. 딕셔너리로 처음 보는 키에 번호를 붙이면 배열 기반 DSU를 그대로 쓸 수 있습니다.
class KeyedDSU:
def __init__(self):
self.index, self.parent, self.size = {}, [], []
def _id(self, key):
if key not in self.index:
self.index[key] = len(self.parent)
self.parent.append(len(self.parent))
self.size.append(1)
return self.index[key]
def find(self, key):
x = self._id(key)
while self.parent[x] != x:
self.parent[x] = self.parent[self.parent[x]]
x = self.parent[x]
return x
def union(self, a, b):
ra, rb = self.find(a), self.find(b)
if ra == rb:
return False
if self.size[ra] < self.size[rb]:
ra, rb = rb, ra
self.parent[rb] = ra
self.size[ra] += self.size[rb]
return True
d = KeyedDSU()
d.union("phone:010-1234", "mail:kim@x.io")
d.union("mail:kim@x.io", "device:A7")
print(d.find("phone:010-1234") == d.find("device:A7")) # Trueparent[a] = b처럼 쓰면 집합이 깨집니다. 반드시 find로 찾은 루트끼리 연결합니다.parent[a] == parent[b]는 경로 압축 전에는 틀릴 수 있습니다. 항상 find(a) == find(b)를 씁니다.class RollbackDSU:
def __init__(self, n):
self.parent, self.size, self.history = list(range(n)), [1] * n, []
def find(self, x): # 경로 압축 없음: 되돌릴 수 있게 유지
while self.parent[x] != x:
x = self.parent[x]
return x
def union(self, a, b):
ra, rb = self.find(a), self.find(b)
if self.size[ra] < self.size[rb]:
ra, rb = rb, ra
self.history.append((rb, ra) if ra != rb else None)
if ra != rb:
self.parent[rb] = ra
self.size[ra] += self.size[rb]
def rollback(self):
last = self.history.pop()
if last:
rb, ra = last
self.parent[rb] = rb
self.size[ra] -= self.size[rb]유니온 파인드는 SciPy, NetworkX, Boost 같은 라이브러리와 이미지 라벨링, 타입 추론, 데이터 정제에 이미 쓰이고 있습니다. 루트끼리 연결하고 find로 비교하며 반복문으로 구현한다는 세 가지만 지켜도 대부분의 실수를 피할 수 있습니다. 되돌리기가 필요하면 롤백 DSU를 떠올리세요.
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.
첫 댓글을 남겨 보세요.