본문 바로가기
알고리즘

최소 공통 조상(LCA : Lowest Common Ancestor)

by 출근길 2018. 7. 18.

1. 트리 깊이를 구할 K값을 구함

2. 깊이를 저장할 배열 depth

3. 깊이에 따른 조상값을 저장할 배열 par

4. 간선정보 저장

5. 완전탐색(bfs 등)을 통하여 depth, par 배열 갱신

6. 조상배열(par) 을 깊이에 따라 갱신 par[i][j] = par[i-1][par[i-1][j]];

7. 문제에 따른 lca(a, b)수행

1) a를 depth가 더 깊도록 swap

2) a, b의 높이 차이 계산 diff

3) diff를 2진법으로 변환하여 a값을 갱신

4) a 와 b의 값이 동일하면 a를 return

5) 다르면 K보다 1개 작은 차수부터 부모차수까지 이동하면서 값 갱신

6) a의 부모 (par[0][a]) 값이 lca


import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class Main {
	/*
	 * LCA2
	 * https://www.acmicpc.net/problem/11438
	 */

	static int K = 0;
	static int[][] par;
	static int[] depth;
	static ArrayList[] adjList;
	
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
		StringTokenizer st;
		
		int N = Integer.parseInt(br.readLine());
		
		//K max값 구하기
		for (int i = N; i >= 1; i/=2, K++); 		
		
		//초기화, 0부터 필요
		depth = new int[N+1];
		par = new int[K+1][N+1];
		adjList = new ArrayList[N+1];
		for (int i = 0; i <= N; i++) { 				
			adjList[i] = new ArrayList();
			depth[i] = -1;
		}
		
		//데이터 입력. 간선노드 만들기
		for (int i = 1; i < N; i++) {
			st = new StringTokenizer(br.readLine());
			int a = Integer.parseInt(st.nextToken());
			int b = Integer.parseInt(st.nextToken());
			
			adjList[a].add(b);			
			adjList[b].add(a);
		}
		
		//bfs 탐색등으로 depth, par 테이블 채우기
		bfs(1);
		
		//par테이블 채우기(K 승)
		
		for (int i = 1; i <= K; i++) {
			for (int j = 1; j <= N; j++) {
				par[i][j] = par[i-1][par[i-1][j]];
			}
		}
		
		int M = Integer.parseInt(br.readLine());
		for (int i = 1; i <= M; i++) {
			st = new StringTokenizer(br.readLine());
			int a = Integer.parseInt(st.nextToken());
			int b = Integer.parseInt(st.nextToken());
			
			int result = lca(a, b);
			bw.write(result+"\n");
		}		
		
		br.close();
		bw.flush();
		bw.close();
	}
	
	
	static int lca(int a, int b) {
		//depth 가 a가 더 낮으면 더 깊은것으로 swap
		if(depth[a] < depth[b]) {
			int tmp = a;
			a = b;
			b = tmp;
		}
		
		//높이 차이 계산
		int diff = depth[a] - depth[b];

		//ex) diff = 11
		// 11 = 2^3 + 2^1 + 2^0
		int k = 0;
		while (diff >= 1) {
			if (diff%2 == 1) {
				a = par[k][a];
			}
			
			diff /= 2;
			k++;
		}
		
		//위로 올라가 b와 동일한 값이 나오면 a는  LCA 임
		if (a == b) {
			return a;
		}
		
		//남은 부분은 남은 값으로 점프
		for (k = K-1; k > -1; k--) {
			if (par[k][a] != par[k][b]) {
				a = par[k][a];
				b = par[k][b];
			}
		}
		
		return par[0][a];
	}
	
	static void bfs(int root) {
		
		Queue que = new LinkedList();
		que.add(root);
		
		// 최초 시작점의 부모는 0
		par[0][root] = 0;
		
		while(!que.isEmpty()) {
			int cur = que.poll();
			for (int next : adjList[cur]) {
				
				//다음점 값과 현재나온값의 부모값이 동일하면 continue. 양방향 노드여서 부모자식간 값이 같을 수 있음
				if(next == par[0][cur]) {
					//System.out.println(next+" "+par[0][cur]);
					continue;
				}
				
				//다음노드 깊이는 부모노드+1
				depth[next] = depth[cur] + 1;
				
				//다음노드의 부모값은 현재값;
				par[0][next] = cur;
				que.add(next);				
			}
		}
	}
}



'알고리즘' 카테고리의 다른 글

벨만포드  (0) 2018.09.11
최장 증가 수열 (LIS : Longest Increasing Subsequence)  (0) 2018.07.19
에라토스 테네스의 체  (0) 2018.07.18
페르마의 소정리  (0) 2018.07.17
Union-Find  (0) 2018.07.17
위상정렬 (Topological Sort)  (0) 2018.07.17
SPFA (Shortest Path Faster Algorithm)  (0) 2018.07.17
인덱스 트리 (Indexed Tree)  (0) 2018.07.17