최소 공통 조상 (Lowest Common Ancestor, LCA)
개념
- 두 노드의 공통된 조상 중 가장 가까운 조상을 찾는 문제
동작 과정
- 모든 노드에 대한 깊이(depth) 계산
- 최소 공통 조상을 찾을 두 노드 확인
- 먼저 두 노드의 깊이(depth)가 같아질 때까지 거슬러 올라감
- 이후 부모가 같아질 때까지 반복적으로 두 노드의 부모 방향으로 거슬러 올라감
- 모든 LCA(a,b) 연산에 대해 2번 과정 반복
연산 과정
- DFS를 이용해 모든 노드에 대해 깊이(depth) 계산
소스 코드 (Python)
import sys
sys.setrecursionlimit(int(1e5))
n = int(input())
parent = [0] * (n + 1)
d = [0] * (n + 1)
c = [0] * (n + 1)
graph = [[] for _ in range(n + 1)]
for _ in range(n - 1):
a, b = map(int, input().split())
graph[a].append(b)
graph[b].append(a)
def dfs(x, depth):
c[x] = True
d[x] = depth
for y in graph[x]:
if c[y]:
continue
parent[y] = x
dfs(y, depth + 1)
def lca(a, b):
while d[a] != d[b]:
if d[a] > d[b]:
a = parent[a]
else:
b = parent[b]
while a != b:
a = parent[a]
b = parent[b]
return a
dfs(1, 0)
m = int(input())
for i in range(m):
a, b = map(int, input().split())
print(lca(a, b))
시간 복잡도
- O(NM)
- 매 쿼리마다 부모 방향으로 거슬러 올라가기 위해 최악의 경우 O(N)
- 속도를 더 빠르게 하려면 ?
- O(MlogN)
- 2의 제곱 형태로 거슬러 올라가도록 하면 매 쿼리마다 부모를 거슬러 올라가기 위해 O(logN)
- ex. 8칸 -> 4칸 -> 2칸 -> 1칸
- 따라서 메모리를 조금 더 사용해 각 노드에 대해 2i번째 부모에 대해 기록
- 다이나믹 프로그래밍(dynamic programming)을 이용해 개선 가능
개선된 LCA 소스코드 (Python)
import sys
input = sys.stdin.readline
sys.setrecursionlimit(int(1e5))
LOG = 21
n = int(input())
parent = [[0] * LOG for _ in range(n + 1)]
d = [0] * (n + 1)
c = [0] * (n + 1)
graph = [[] for _ in range(n + 1)]
for _ in range(n - 1):
a, b = map(int, input().split())
graph[a].append(b)
graph[b].append(a)
def dfs(x, depth):
c[x] = True
d[x] = depth
for y in graph[x]:
if c[y]:
continue
parent[y][0] = x
dfs(y, depth + 1)
def set_parent():
dfs(1, 0)
for i in range(1, LOG):
for j in range(1, n + 1):
parent[j][i] = parent[parent[j][i - 1]][i - 1]
def lca(a, b):
if d[a] > d[b]:
a, b = b, a
for i in range(LOG - 1, -1, -1):
if d[b] - d[a] >= (1 << i):
b = parent[b][i]
if a == b:
return a;
for i in range(LOG - 1, -1, -1):
if parent[a][i] != parent[b][i]:
a = parent[a][i]
b = parent[b][i]
return parent[a][0]
set_parent()
m = int(input())
for i in range(m):
a, b = map(int, input().split())
print(lca(a, b))