← 目录 / 算法文档 · 模块十九 拓展专题 / 19.1 LCA 最近公共祖先

19.1 LCA 最近公共祖先

树上两个节点最近的共同祖先——朴素做法每次查询要 O(n),倍增思想能把它压缩到 O(log n)。

本页目录
① 什么是 LCA

给定一棵有根树,最近公共祖先(Lowest Common Ancestor,简称 LCA)指的是:给定两个节点 uv,同时是它们祖先(包括自身)的节点里,深度最大的那一个。

最直接的做法:把 uv 中较深的那个先向上跳到和另一个同样的深度,然后两个节点一步一步同时往上跳,直到两者相遇,相遇的位置就是 LCA。这个朴素做法每次查询最坏要跳 O(n) 步——如果需要回答很多次 LCA 查询,会很慢。树上倍增能把每次查询优化到 O(log n)

② 核心思想:树上倍增

倍增的核心是预处理一个数组 up[u][k],表示节点 u 向上跳 2^k能到达的祖先。这个数组可以递推算出:up[u][k] = up[ up[u][k-1] ][k-1]——"跳 2^k 步"等于"先跳 2^(k-1) 步,再跳 2^(k-1) 步"。有了这张表,任何"向上跳 x 步"都能拆成若干个 2 的次方之和(二进制拆分),用倍增表拼出来,只需要 O(log n) 次跳跃。

步骤做什么
① 预处理DFS 一遍树,算出每个节点的深度 depth[u] 和直接父亲 up[u][0];再递推出 up[u][k](k=1,2,3...)
② 对齐深度查询 LCA(u,v) 时,先把较深的那个节点向上跳,跳到和另一个节点同样的深度
③ 同时倍增上跳从大到小枚举 k,如果 u、v 跳 2^k 步之后仍然不同,就把两者都跳上去(保证不会跳过 LCA)
④ 收尾循环结束后,u、v 的直接父亲就是 LCA
③ 图解:一棵具体树的完整查询过程

用一棵 7 个节点的树演示,节点旁边标出深度:

树结构(深度从根节点 0 开始)
1depth=0
2depth=1
4depth=2
7depth=3
5depth=2
3depth=1
6depth=2
up 数组(预处理结果,0 表示"没有祖先")
uup[u][0](跳1步)up[u][1](跳2步)up[u][2](跳4步)
4210
6310
7420

查询 LCA(7, 6)depth[7]=3depth[6]=27 更深,先把 7 往上跳 1 步对齐深度:

LCA(7, 6) 完整查询过程
步骤操作结果
对齐深度7 跳 2⁰=1 步:7 → up[7][0] = 4u=4, v=6(同深度 2)
k=2(跳4步)up[4][2]=0 与 up[6][2]=0 相同不跳(跳了会越过 LCA)
k=1(跳2步)up[4][1]=1 与 up[6][1]=1 相同不跳
k=0(跳1步)up[4][0]=2 与 up[6][0]=3 不同都跳:u=2, v=3
循环结束,u、v 已经是兄弟节点,答案 = up[2][0] = 1
结果 LCA(7,6)=1——验证一下:7 的祖先链是 7→4→2→16 的祖先链是 6→3→1,两条链唯一的公共节点就是 1,符合预期。k=2k=1 时之所以"相同就不跳",是因为如果这时候跳了,uv 可能会一路跳到 LCA上面,跳过了真正的答案——只有在"跳了之后依然不同"时才安全地跳,这样循环结束时,uv 一定是 LCA 的两个直接孩子(或者其中一个就是原来的 u/v,另一种边界情况见 ⑤)。
④ 完整代码
C++ · LCA(树上倍增)
1const int LOG = 20; // 2^20 远大于常见的节点数,足够用
2vector<int> children[MAXN];
3int up[MAXN][LOG], depth[MAXN];
4
5void DFS(int u, int par)
6{
7 up[u][0] = par;
8 for (int k = 1; k < LOG; k++)
9 up[u][k] = up[ up[u][k-1] ][k-1]; // ★ 跳 2^k 步 = 先跳 2^(k-1),再跳 2^(k-1)
10 for (int v : children[u])
11 {
12 depth[v] = depth[u] + 1;
13 DFS(v, u);
14 }
15}
16
17int LCA(int u, int v)
18{
19 if (depth[u] < depth[v]) swap(u, v); // 保证 u 更深(或一样深)
20 int diff = depth[u] - depth[v];
21 for (int k = 0; k < LOG; k++)
22 if (diff >> k & 1) u = up[u][k]; // ★ 把深度差 diff 按二进制拆分,跳到同一深度
23 if (u == v) return u; // 对齐后 u、v 重合,说明 v 本来就是 u 的祖先
24
25 for (int k = LOG - 1; k >= 0; k--) // ★ 从大到小尝试跳,跳了之后不同才跳
26 if (up[u][k] != up[v][k])
27 {
28 u = up[u][k];
29 v = up[v][k];
30 }
31 return up[u][0]; // 循环结束,u、v 是 LCA 的两个孩子,它们的父亲就是答案
32}
💡
第 21~22 行是"深度对齐",用的是二进制拆分的思路:假设深度差 diff=5(二进制 101),需要跳 5 步,会先判断第 0 位是 1(跳 2⁰=1 步),第 2 位是 1(跳 2²=4 步),总共跳了 1+4=5 步——用倍增表拼出任意步数,只需要 O(log n) 次跳跃,而不是跳 5 次单步。第 25~30 行"两个指针同时倍增"用的是同样的思路,只是判断条件从"深度差的某一位是不是 1"变成了"跳了之后 u、v 还相不相同"。
⑤ 复杂度与常见陷阱

预处理 up 数组需要 O(n log n)(每个节点算 log n 个倍增值);每次查询只需要 O(log n)(对齐深度、同步跳跃各一次 log n 循环)。

忘记处理"v 本来就是 u 的祖先"这种边界情况:第 23 行 if (u == v) return u; 是必要的——对齐深度之后,如果两个节点已经重合,说明较浅的那个节点本来就是较深节点的祖先,直接返回即可,不需要再进入下面"同时跳跃"的循环(那个循环假设 u ≠ v,如果不加这个判断,跳跃过程可能会得出错误的结果或者浪费计算)。
up 数组的 LOG 开得不够大:LOG 需要满足 2^LOG ≥ n(节点数),如果开小了,深度差较大的两个节点没法通过二进制拆分完全对齐深度,跳跃会出错。竞赛中通常直接取一个足够大的固定值(比如 20,对应节点数上限约 100 万),不需要精确计算。
多组查询时,重复对同一棵树做 DFS 预处理:up 数组和 depth 数组只依赖树的结构本身,和具体查询哪两个节点无关——只需要在处理所有查询之前,对树做一次 DFS 预处理,之后的每次 LCA 查询都直接复用这份预处理结果,不需要也不应该每次查询都重新 DFS 一遍。
🏆
接下来:19.2 节的博弈论会换到一个完全不同的话题——两个玩家轮流做决策的游戏,怎么判断谁能获胜、怎么找到必胜策略,会用到和之前完全不同的分析工具。