LCA 알고리즘

LCA 알고리즘이란?

Imagem de capa

LCA(Lowest Common Ancestor) 알고리즘이란?

LCA 알고리즘이란 최소 공통 조상을 찾는 알고리즘입니다. 자세한 설명은 아래 링크를 통해서 확인해봅시다. 여기는 c++로 되어있지만 저는 java로 바꾼 코드로 설명해드리겠습니다. 이 링크에서도 주석을 확인해주세요!

LCA 알고리즘


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;
	}

}