서로소 집합: 공통 원소가 없는 두 집합
서로소 집합 자료구조
합집합(Union) 연산을 확인하여, 서로 연결된 두 노드 A, B를 확인함
a. A와 B의 루트 노드 A’, B’를 각각 찾음
b. A’를 B’의 부모 노드로 설정함
모든 합집합(Union) 연산을 처리할 때까지 1번의 과정을 반복함
서로소 집합 자료구조의 동작 과정
서로소 집합 자료구조에서는 연결성을 통해 손쉽게 집합의 형태를 확인할 수 있음
기본적인 형태의 서로소 집합 자료구조에서는 루트 노드에 즉시 접근할 수 없음
노드3의 루트를 찾기 위해서는 노드2를 거쳐 노드1에 접근해야함
코드
# 특정 원소가 속한 집합을 찾기
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
# 노드의 개수와 간선(Union 연산)의 개수 입력 받기
v, e = map(int, input().split())
parent = [0] * (v + 1) # 부모 테이블 초기화하기
# 부모 테이블상에서, 부모를 자기 자신으로 초기화
for i in range(1, v + 1):
parent[i] = i
# Union 연산을 각각 수행
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=' ')
기본적인 구현 방법의 문제점
합집합(Union) 연산이 편향되게 이루어지는 경우 찾기(Find)함수가 비효율적으로 동작함
최악의 경우에 찾기(Find) 함수가 모든 노드를 다 확인하게 되어 시간복잡도가 O(V)임
서로소 집합 자료구조: 경로 압축
찾기(Find) 함수를 재귀적으로 호출한 뒤에 부모 테이블 값을 바로 갱신함
# 특정 원소가 속한 집합을 찾기
def find_parent(parent, x):
# 루트 노드가 아니라면, 루트 노드를 찾을 때까지 재귀적으로 호출
if parent[x] != x:
parent[x] = find_parent(parent, parent[x])
return parent[x]
# 특정 원소가 속한 집합을 찾기
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
# 노드의 개수와 간선(Union 연산)의 개수 입력 받기
v, e = map(int, input().split())
parent = [0] * (v + 1) # 부모 테이블 초기화하기
# 부모 테이블상에서, 부모를 자기 자신으로 초기화
for i in range(1, v + 1):
parent[i] = i
# Union 연산을 각각 수행
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 함수)
a. 루트 노드가 서로 다르다면 두 노드에 대하여 합집합(Union) 연산을 수행함
b. 루트 노드가 서로 같다면 사이클(Cycle)이 발생한 것
그래프에 포함되어 있는 모든 간선에 대하여 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
# 노드의 개수와 간선(Union 연산)의 개수 입력 받기
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
# 사이클이 발생하지 않았다면 합집합(Union) 연산 수행
else:
union_parent(parent, a, b)
if cycle:
print("사이클이 발생했습니다.")
else:
print("사이클이 발생하지 않았습니다.")
참고: 이것이 취업을 위한 코딩 테스트다 with 파이썬 (취업과 이직을 결정하는 알고리즘 인터뷰 완벽 가이드), 유튜브 강의 영상