서로소 집합

그래프

노드와 노드 사이에 연결된 간선의 정보를 가지고 있는 자료구조를 의미

  • 서로 다른 개체가 연결되어 있다는 내용이 나오면 그래프 알고리즘으로 의심해야된다!

구현 방법

  • 인접 행렬: 2차원 배열을 사용하는 방식
  • 인접 리스트: 리스트를 사용하는 방식

서로소 집합

  • 수학에서의 의미: 공동 원소가 없는 두 집합을 의미
  • 서로소 집합 자료구조: 서로소 부분 집합들로 나누어진 원소들의 데이터를 처리하기 위한 자료구조 – unionfind 연산으로 조작 가능
    1. union 연산: 2개의 원소가 포함된 집합을 하나의 집합으로 합치는 연산
    2. find 연산: 특정한 원소가 속한 집합이 어떤 집합인지 알려주는 연산

서로소 집합 자료구조

  • 트리 자료구조로 집합 표현

서로소 집합 계산 알고리즘

  1. Union연산을 확인하여 연결된 두 노드 A, B를 확인
    • A와 B의 루트노드 A’, B’를 찾는다.
    • A’를 B’의 부모 노드로 설정한다.
  2. 모든 union 연산을 처리할 때까지 반복
  • 실제 구현 시 A’와 B’ 중 번호가 작은 원소가 부모 노드가 되도록 구현하는 경우가 많다.
예시

1 2 3 4 5 6

연결 노드: 1-2, 2-3. 1-4,, 5-6

1. 노드 개수 V크기의 부모 테이블 초기화

노드 번호  
부모

2. union 1, 4

노드 번호  
부모
  • 첫번째 union연산으로 1과 4를 합친다.
  • 루트 노드 4의 부모를 1로 설정한다.

3. union 2, 3

노드 번호  
부모
  • union연산으로 2과 3를 합친다.
  • 루트 노드 3의 부모를 2로 설정한다.

4. union 2, 4

노드 번호  
부모
  • union연산으로 2과 4를 합친다.
  • 노드 2와 노드 4의 루트 노드를 찾는다.
  • 노드 2의 루트 노드는 2, 4의 루트 노드는 1
  • 루트 노드 2의 부모를 1로 설정

5. union 5, 6

노드 번호  
부모
  • 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))

서로소 집합을 활용한 사이클 판별

집합을 합치는 과정을 반복하여 무방향 그래프 내의 사이클 판별 가능

과정

  1. 각 간선을 확인하며 두 노드의 루트 노드 확인
    • 루트 노드가 서로 다른 경우: 두 노드 union
    • 루트 노드가 서로 같은 경우: 사이클
  2. 그래프에 포함된 모든 간선에 대해 1번 과정 수행

예시

1, 2, 3

연결된 노드: 1-2, 2-3, 3-1

  • 1, 2, 3이 사이클을 이루고 있는 경우

1. 부모 테이블 초기화

노드 번호  
부모

2. 간선 (1, 2) 확인

  • 노드 1과 노드 2의 루트 노드 = 1, 2
  • 노드 2의 부모 노드를 1로 설정
노드 번호  
부모

3. 간선 (1, 3) 확인

  • 노드 1과 노드 3의 루트 노드 = 1, 3
  • 노드 3의 부모 노드를 1로 설정
노드 번호  
부모

4. 간선 (2, 3) 확인

  • 노드 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")

homebdy
homebdy 개발에 이제 막 발 담근 사람. 개발에 이제 막 발 담근 사람. 개발에 이제 막 발 담근 사람. 개발에 이제 막 발 담근 사람.
comments powered by Disqus