Categories
Algorithm🧩
백준 📝
BookReview📕
CleanCode✨
Network 📨
Database 🗄
DevOps☁️
에러 일기📕
Etc💬
Fishy Fish 🎣
Spring🌱
서로소 집합
그래프
노드와 노드 사이에 연결된 간선의 정보를 가지고 있는 자료구조를 의미
- 서로 다른 개체가 연결되어 있다는 내용이 나오면 그래프 알고리즘으로 의심해야된다!
구현 방법
- 인접 행렬: 2차원 배열을 사용하는 방식
- 인접 리스트: 리스트를 사용하는 방식
서로소 집합
- 수학에서의 의미: 공동 원소가 없는 두 집합을 의미
- 서로소 집합 자료구조: 서로소 부분 집합들로 나누어진 원소들의 데이터를 처리하기 위한 자료구조
–
union과find연산으로 조작 가능- union 연산: 2개의 원소가 포함된 집합을 하나의 집합으로 합치는 연산
- find 연산: 특정한 원소가 속한 집합이 어떤 집합인지 알려주는 연산
서로소 집합 자료구조
- 트리 자료구조로 집합 표현
서로소 집합 계산 알고리즘
- Union연산을 확인하여 연결된 두 노드 A, B를 확인
- A와 B의 루트노드 A’, B’를 찾는다.
- A’를 B’의 부모 노드로 설정한다.
- 모든 union 연산을 처리할 때까지 반복
- 실제 구현 시 A’와 B’ 중 번호가 작은 원소가 부모 노드가 되도록 구현하는 경우가 많다.
예시
1 2 3 4 5 6
연결 노드: 1-2, 2-3. 1-4,, 5-6
1. 노드 개수 V크기의 부모 테이블 초기화
| 노드 번호 | 1 | 2 | 3 | 4 | 5 | 6 |
| 부모 | 1 | 2 | 3 | 4 | 5 | 6 |
2. union 1, 4
| 노드 번호 | 1 | 2 | 3 | 4 | 5 | 6 |
| 부모 | 1 | 2 | 3 | 1 | 5 | 6 |
- 첫번째 union연산으로 1과 4를 합친다.
- 루트 노드 4의 부모를 1로 설정한다.
3. union 2, 3
| 노드 번호 | 1 | 2 | 3 | 4 | 5 | 6 |
| 부모 | 1 | 2 | 2 | 1 | 5 | 6 |
- union연산으로 2과 3를 합친다.
- 루트 노드 3의 부모를 2로 설정한다.
4. union 2, 4
| 노드 번호 | 1 | 2 | 3 | 4 | 5 | 6 |
| 부모 | 1 | 1 | 2 | 1 | 5 | 6 |
- union연산으로 2과 4를 합친다.
- 노드 2와 노드 4의 루트 노드를 찾는다.
- 노드 2의 루트 노드는 2, 4의 루트 노드는 1
- 루트 노드 2의 부모를 1로 설정
5. union 5, 6
| 노드 번호 | 1 | 2 | 3 | 4 | 5 | 6 |
| 부모 | 1 | 1 | 2 | 1 | 5 | 5 |
- union연산으로 5과 6를 합친다.
- 노드 5와 노드 6의 루트 노드를 찾는다.
- 루트 노드 6의 부모를 5로 설정
이 예시에서 3의 부모 노드는 2이지만 2의 부모 노드는 1이므로 노드 3의 루트 노드는 1이다
- 노드 3의 루트를 찾으려면 부모노드인 2로 이동한 후, 노드 2의 부모를 다시 확인해야 최종 루트 노드를 확인할 수 있다.
소스코드
# 특정 원소가 속한 집합을 찾는다.
def find_parent(parent, x):
#루트 노드가 아니면 루트 노드를 찾을 때까지 재귀 호출
if parent[x] != x:
return find_parent(parent, parent[x])
return x
# 두 원소가 속한 집합을 합친다.
def union_parent(parent, a, b):
a = find_parent(parent, a)
b = find_parent(parent, b)
if a < b:
parent[b] = a
else:
parent[a] = b
v, e= map(int, input().split())
parent = [0] * (v+1)
for i in range(1, v+1):
parent[i] = i
for i in range(e):
a, b = map(int, input().split())
union_parent(parent, a, b)
print('각 원소의 집합: ', end='')
for i in range(1, v+1):
print(find_parent(parent, i), end = ' ')
print()
print('부모 테이블: ', end = '')
for i in range(1, v+1):
print(parent[i], end = ' ')
-
find함수가 비효율 적으로 동작– 최악의 경우 모든 노드를 다 확인하여야 하므로 시간 복잡도가 O(V)
-
경로압축기법을 사용하여 최적화할 수 있다.– find 함수를 재귀적으로 호출한 뒤 부모 테이블 값을 갱신하는 기법
경로 압출 기법을 사용해 개선한 코드
def find_parent(parent, x):
if parent[x] != x:
parent[x] = find_parent(parent, parent[x])
return parent[x]
-
노드의 개수가 V개, 최대 V -1개의 union 연산과 M개의 find 연산이 가능할 때 시간 복잡도
– O(B+M(1+log_(2-M/V) V))
서로소 집합을 활용한 사이클 판별
집합을 합치는 과정을 반복하여 무방향 그래프 내의 사이클 판별 가능
과정
- 각 간선을 확인하며 두 노드의 루트 노드 확인
- 루트 노드가 서로 다른 경우: 두 노드 union
- 루트 노드가 서로 같은 경우: 사이클
- 그래프에 포함된 모든 간선에 대해 1번 과정 수행
예시
1, 2, 3
연결된 노드: 1-2, 2-3, 3-1
- 1, 2, 3이 사이클을 이루고 있는 경우
1. 부모 테이블 초기화
| 노드 번호 | 1 | 2 | 3 |
| 부모 | 1 | 2 | 3 |
2. 간선 (1, 2) 확인
- 노드 1과 노드 2의 루트 노드 = 1, 2
- 노드 2의 부모 노드를 1로 설정
| 노드 번호 | 1 | 2 | 3 |
| 부모 | 1 | 1 | 3 |
3. 간선 (1, 3) 확인
- 노드 1과 노드 3의 루트 노드 = 1, 3
- 노드 3의 부모 노드를 1로 설정
| 노드 번호 | 1 | 2 | 3 |
| 부모 | 1 | 1 | 1 |
4. 간선 (2, 3) 확인
- 노드 2과 노드 3의 루트 노드 = 1, 1
- 두 노드의 부모 노드가 모두 1
- 사이클이 발생되었다는 것을 알 수 있음
| 노드 번호 | 1 | 2 | 3 |
| 부모 | 1 | 1 | 1 |
소스 코드
def find_parent(parent, x):
if parent[x] != x:
parent[x] = find_parent(parent, parent[x])
return parent[x]
# 두 원소가 속한 집합을 합친다.
def union_parent(parent, a, b):
a = find_parent(parent, a)
b = find_parent(parent, b)
if a < b:
parent[b] = a
else:
parent[a] = b
v, e= map(int, input().split())
parent = [0] * (v+1)
for i in range(1, v+1):
parent[i] = i
cycle = False
for i in range(e):
a, b = map(int, input().split())
if find_parent(parent, a) == find_parent(parent, b):
cycle = True
break
else:
union_parent(parent, a, b)
if cycle:
print("사이클 발생")
else:
print("사이클 X")