22 : 상호배타 집합들
작성:
상호배타 집합들
1. 상호배타 집합 (Disjoint Set)
- 서로 중복 포함된 원소가 없는 집합들. (교집합이 없음, 서로소)
- 집합에 속한 하나의 특정 원소를 통해 각 집합들을 구분한다.
- 특정 원소 - 대표자 (Representative)
- 상호배타 집합을 표현하는 방법
- 연결 리스트
- 찾기 연산의 시간복잡도는 O(1)이지만 합치기 연산은 O(n)이다.
- 트리
- 이를 Union-Find 자료구조라고 한다.
- 연결 리스트
2. 상호배타 집합의 연산
- Union-Find 자료구조
- 초기화 - n개의 원소가 자기 자신으로 구성된 집합을 만들도록 한다.
- 찾기 (find) - 어떤 원소 x가 속한 집합을 반환한다. (집합의 대표자를 알아낸다.)
- 합치기 (Union) - x 원소가 속한 집합과 y 원소가 속한 집합을 하나의 집합으로 합친다.
3. 상호배타 집합 연산 최적화
a. ranks
- 두 트리를 합칠 때 항상 높이가 더 낮은 트리를 더 높은 트리 밑에 집어넣는다.
- 합치기 연산 + 찾기 연산의 시간복잡도가 O(n)에서 O(logn)으로 된다.
- 트리의 높이가 h-1이기 위해 최소 n개의 노드가 필요하다면, 높이가 하나 높아지기 위해서는 최소 2n개의 노드가 필요하기 때문이다.
b. parents
- 경로 압축 (Path Compression)
- find를 통해 루트까지 올라가는 경로 상에 있는 모든 노드들이 루트 노드(부모 노드)를 가리키게 한다.
- 현실적인 모든 입력에 대해 상수 시간에 동작한다.
4. 상호배타 집합 활용 예시
- 그래프의 연결성 확인
- KRUSKAL Minimum Spanning Tree (MST) 알고리즘
- 가장 큰 집합 추적하기
- 집합의 노드 개수가 몇 개 이상이 되는 시점 찾기 (각 트리의 노드의 개수를 담는 배열을 추가해서 값을 갱신하는 방식으로 가능하다.)
5. 상호배타 집합 구현
a. 연결 리스트
class Node:
def __init__(self, val):
self.parent = self
self.next = None
self.data = val
def __repr__(self):
return self.data
class linkedlist:
def __init__(self, val):
self.head = Node(val)
self.length = 1
self.tail = self.head
def union(self, llist):
assert self.length >= llist.length
self.tail = llist.head
temphead = llist.head
while temphead:
temphead.parent = self.head
temphead = temphead.next
self.length += llist.length
def find(self):
return self.head.parent
elements1 = ['a', 'd', 'e']
elements2 = ['b', 'f']
linkedlist1 = linkedlist(elements1[0])
linkedlist1.union(linkedlist(elements1[1]))
linkedlist1.union(linkedlist(elements1[2]))
linkedlist2 = linkedlist(elements2[0])
linkedlist2.union(linkedlist(elements2[1]))
print('before union')
print(linkedlist1.find())
print('length:', linkedlist1.length)
linkedlist1.union(linkedlist2)
print('after union')
print(linkedlist1.find())
print('length:', linkedlist1.length)
# 출력
before union
a
length: 3
after union
a
length: 5
b. 트리
def Find(x):
if parents[x] == x:
return x
parents[x] = Find(parents[x])
return parents[x]
def Union(x, y):
xroot = Find(x)
yroot = Find(y)
if ranks[xroot] >= ranks[yroot]:
parents[yroot] = xroot # y를 루트로 하는 트리를 x를 루트로 하는 트리에 합친다.
else:
parents[xroot] = yroot # x를 루트로 하는 트리를 y를 루트로 하는 트리에 합친다.
if ranks[xroot] == ranks[yroot]:
ranks[xroot] += 1
parents = [i for i in range(10)]
# 원소가 10개 있는 경우.
# i번째 항목의 값이 i라고 선언하는 것 자체가 각각의 트리를 만든 것이라 보면 된다.
# 인덱스는 각 항목, 값은 부모 노드로 볼 수 있다.
ranks = [0 for i in range(10)]
# 루트 노드가 i인 트리의 랭크 값 저장
print('Initial root nodes and ranks : ')
print(parents)
print(ranks)
print('------------------------------')
Union(2, 3)
Union(4, 5)
Union(2, 4)
print('Root nodes and ranks after Union : ')
print(parents)
print(ranks)
print('------------------------------')
print(Find(5))
print('After find(5) : ')
print(parents)
print('Root node for 5 has been updated')
# 출력
Initial root nodes and ranks :
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
------------------------------
Root nodes and ranks after Union :
[0, 1, 2, 2, 2, 4, 6, 7, 8, 9]
[0, 0, 2, 0, 1, 0, 0, 0, 0, 0]
------------------------------
2
After find(5) :
[0, 1, 2, 2, 2, 2, 6, 7, 8, 9]
Root node for 5 has been updated
출처: SW Expert Academy - Learn - Course - Programming Advanced &
알고리즘 문제 해결 전략
댓글남기기