6 min to read
LCA 알고리즘
LCA 알고리즘이란?

LCA(Lowest Common Ancestor) 알고리즘이란?
LCA 알고리즘이란 최소 공통 조상을 찾는 알고리즘입니다. 자세한 설명은 아래 링크를 통해서 확인해봅시다. 여기는 c++로 되어있지만 저는 java로 바꾼 코드로 설명해드리겠습니다. 이 링크에서도 주석을 확인해주세요!
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.StringTokenizer;
public class Main{
static int N, M;
static int[] visited, dist;//방문여부/깊이
static int[][] parent;
static List<Integer>[] adjList; //인접한 노드를 저장합니다.
static long ans;
final static int MAX = 16;//아무리 최악의 경우라도 가장 아래의 노드가 2^16을 통해 루트 노드를 향해 갈 수 있다. 2^17을 해버리면 루트 노드를 넘어가버린다.
public static void main(String[] Args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
N = Integer.parseInt(br.readLine());
dist = new int[N+1];
visited = new int[N+1];
parent = new int[MAX+1][N+1];
adjList = new ArrayList[N+1];
//인접한 노드끼리의 리스트에 넣어줍니다.
for (int i=1; i<N; i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
int a = Integer.parseInt(st.nextToken());
int b = Integer.parseInt(st.nextToken());
if (adjList[a] == null) {
adjList[a] = new ArrayList<Integer>();
}
adjList[a].add(b);
if (adjList[b] == null) {
adjList[b] = new ArrayList<Integer>();
}
adjList[b].add(a);
}
// bfs를 통하여 각 노드의 depth 를 구하고 sparseTabel의 초기값을 정한다.
//Sparse Table이란 전체 N개의 원소들 중 임의의 원소 A에 대해, A를 바로 앞서서 있는 원소가 1개 이하 존재하는 상황에서, 원소 A의 K번째 앞에 있는 원소를 찾는 경우 사용하는 방법입니다.
bfs(1);
// sparse tabel을 DP를 이용하여 구한다.
// 2^(k+1) = 2^k * 2
for (int k=0; k<MAX; k++) {
for (int i=1; i<=N; i++) {
parent[k+1][i] = parent[k][parent[k][i]]; //i의 2^(k+1)번째 조상은 parent[k][i]의 2^k번째 조상과 같다. 는 점화식을 적용한 것입니다.
}
}
M = Integer.parseInt(br.readLine());
for (int i=1; i<=M; i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
int a = Integer.parseInt(st.nextToken());
int b = Integer.parseInt(st.nextToken());
System.out.println(String.valueOf(lca(a, b)));
}
}
public static void bfs(int a) {
Queue<Integer> que = new LinkedList<Integer>();
//뿌리는 1로 설정, 깊이 0
que.add(a);
visited[a] = 1;
dist[a] = 0;
while (!que.isEmpty()) {
int cur = que.poll();
if (adjList[cur] != null) {
for (int next : adjList[cur]) {
if (visited[next] == 0) {
que.add(next);
visited[next] = 1;
dist[next] = dist[cur] + 1;
parent[0][next] = cur; //next의 2^0 조상은 cur
}
}
}
}
}
public static int lca(int a, int b) {
//깊이는 왼쪽값이 더 깊게 해줍니다.
if (dist[a] < dist[b]) {
return lca(b, a);
}
int diff = dist[a] - dist[b];
int k=0;
//while문을 통해 같은 깊이의 a값을 찾는 과정입니다.
while (diff > 0) {
if (diff % 2 == 1) {
a = parent[k][a]; //a의 2^k번째 조상을 a에 저장합니다.
}
diff /= 2;
k++;
}
// 서로 같은 깊이로 올렸는데 값이 같다면 그 값이 조상입니다.
if (a == b) {
return a;
}
for (k=MAX; k>=0; k--) {
if (parent[k][a] != parent[k][b]) { //가장 큰값의 k부터 시작해서 a의 2^k번째 조상과 b의 2^k번째 조상이 같지 않을때까지 내려와서 그 값만큼 올려줍니다! 그 전 제곱만큼을 비교할 필요가 없으니 이어서 k값을 감소시켜줍니다. 결국 마지막에는 해당 조상 바로 한칸 아래 노드가 선택됩니다.
a = parent[k][a];
b = parent[k][b];
}
}
a = parent[0][a];//a의 바로 위 조상을 return해주면 끝!
return a;
}
}
Comments