最一般的算法是DFS(DFS本是深度优先搜索,在这里姑且把深度优先遍历也叫做DFS,其实是一种不严谨的说法)。算法很简单,从根节点DFS一遍,按DFS层数k给每个节点标上深度deep[i]=k。然后从U点DFS到V点,找到后回溯,在回溯的路径上找到一个deep[i]最小的节点即为LCA。

import java.io.*;
import java.util.*;

public class Main {
    static int N = (int)1e5 + 5;
    static int[] depth;
    static int[][] fa; 	// father array
    static ArrayList<ArrayList<Integer>> es; // 树的链表表示

    static void dfs(int u){	// 由es更新fa
        for(int i = 0; fa[u][i] != 0; i++)
            fa[u][i + 1] = fa[fa[u][i]][i];
        for (int v : es.get(u)) {
            if (fa[u][0] == v)
                continue;
            depth[v] = depth[u] + 1;
            fa[v][0] = u;
            dfs(v);
        }
    }

    static int lca(int _x, int _y){ // 求lca
        if (depth[_x] < depth[_y])
            return lca(_y, _x);
        int x = _x, y = _y;
        int d = depth[x] - depth[y];
        for (int i = 16; i > -1; --i)
            if ((d & (1 << i)) != 0)
                x = fa[x][i];
        if (x == y)
            return x;
        for (int i = 16; i > -1; --i)
            if (fa[x][i] != fa[y][i]) {
                x = fa[x][i];
                y = fa[y][i];
            }
        return fa[x][0];
    }

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int T = in.nextInt();
        ArrayList<String> res = new ArrayList<>();
        while (T-- > 0){
            depth = new int[N];
            fa = new int[N][20];
            es = new ArrayList<>(N);

            int n = in.nextInt();
            int m = in.nextInt();

            for(int i = 0; i <= n; i++){
                es.add(new ArrayList());
            }

            for(int i = 1; i < n; i++){
                int u = in.nextInt();
                int v = in.nextInt();
                es.get(u).add(v);
                es.get(v).add(u);
            }
            depth[1] = 0;
            dfs(1);

            while (m-- != 0){
                int u = in.nextInt();
                int v = in.nextInt();
                
                int t1 = lca(u, v);
            }
        }
    }
}

当查询量很大时,可采用Tarjan(离线)算法。Tarjan算法的基本思路:

  1. 任选一个点为根节点,从根节点开始。
  2. 遍历该点u所有子节点v,并标记这些子节点v已被访问过。
  3. 若是v还有子节点,返回2,否则下一步。
  4. 合并v到u上。
  5. 寻找与当前点u有询问关系的点v。
  6. 若是v已经被访问过了,则可以确认u和v的最近公共祖先为v被合并到的父亲节点a。

ShengYg

Step after step the ladder is ascended.


Tags • tree