lca倍增 算法 最小公共祖先

本文介绍了一种高效的算法——倍增法来寻找二叉树中两个节点的最近公共祖先(LCA)。通过预处理每个节点2^k次幂的父亲节点及节点深度,利用二进制的思想快速定位LCA。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转:http://www.cnblogs.com/yyf0309/p/5972701.html

找最近公共父节点这问题很容易想到让两节点一起往上走最后相遇,但这样的dfs显然很慢,于是就需要倍增。就是用二进制的思维,以1,2,4,8等2的阶层步长接近答案,比一步一步向上要快很多。
所以要dfs出来点的2^k的父亲节点与该节点的深度。
找lca时先将下面的点升到与另一点同一深度,再用往上倍增找lca。
有两种大同小异的方法:一种是以上一步2倍长的步伐向上试,不行再缩减,找到一个离lca能达到的最近点。另一种是先求出最大深度是2的几次方,再以当前最大步伐向上走。具体看下面代码,喜欢哪种打哪种。。。

这里写图片描述

4和5的LCA就是2

  那怎么求呢?最粗暴的方法就是先dfs一次,处理出每个点的深度
这里写图片描述

 然后把深度更深的那一个点(4)一个点地一个点地往上跳,直到到某个点(3)和另外那个点(5)的深度一样

然后两个点一起一个点地一个点地往上跳,直到到某个点(就是最近公共祖先)两个点“变”成了一个点

这里写图片描述

  不过有没有发现一个点地一个点地跳很浪费时间?

如果一下子跳到目标点内存又可能不支持,相对来说倍增的性价比算是很高的

  倍增的话就是一次跳2i 个点,不难发现深度差为x时,深度更深的那个点就需要跳x个点

于是可以写出这段代码

if(depth[a] < depth[b])    swap(a, b);
int c = depth[a] - depth[b];
for(int i = 0; i <= 14; i++){
    if(c & (1 << i)){
        a = up[a][i];
    }
}

接下来很快就会发现一个很严重的问题:两个点按照这样跳,不能保证一定是最近的

所以倍增找lca的方法是这样的:

  从最大可以跳的步数开始跳(一定是2i),如果跳的到的位置一样,就不跳,如果不一样才跳,每次跳的路程是前一次的一半

这里写图片描述

过程大概就像上图所示,但是执行完了这一段到的点不是最近公共祖先,但是,它们再往上跳一格,就到了

把这一段写成代码,就成了这样:

for(int i = 14; i >= 0; i--){
    if(up[a][i] != up[b][i]){
        a = up[a][i];
        b = up[b][i];
    }
}

前面还需要加上一句特判(当a和b在同一边时,深度浅的那个点就是最近公共祖先)

if(a == b)    return a;

 好了,会求lca了,关键是怎么构造倍增数组。

没有疑问的是向上跳一格就是自己的父节点

f[i][0] = fa[i];

这个是初值,接着可以根据这个推出来其他的,除此之外还要附上初值0,不然有可能会RE

f[i][j] = f[f[i][j - 1]][j - 1];

 就是把这一段路,分成两段已经知道的

  完整代码就是这样的:

Matrix<int> up;
inline void init_bz(){
    up = Matrix<int>(16, n + 1);
    memset(up.p, 0, sizeof(int) * 16 * (n + 1));
    for(int i = 1; i <= n; i++){
        up[i][0] = fa[i];
    }
    for(int j = 1; j <= 14; j++){
        for(int i = 1; i <= n; i++){
            up[i][j] = up[up[i][j - 1]][j - 1];
        }
    }
}
### LCA 倍增算法 Python 实现 LCA (Lowest Common Ancestor) 的倍增算法是一种高效求解树上最近公共祖先的方法。该方法利用动态规划的思想预先处理节点之间的关系,从而可以在 O(log n) 时间复杂度内查询任意两个节点的最近公共祖先。 #### 预处理阶段 预处理的核心在于构建 `dp` 数组,其中 `dp[i][j]` 表示从节点 i 出发向上走 \(2^j\) 步到达的父亲节点编号: ```python from collections import defaultdict, deque def preprocess(n, adj_list, root=0): logn = max(1, int(math.log2(n)) + 1) dp = [[-1]*logn for _ in range(n)] depth = [0] * n def dfs(u, parent=-1): dp[u][0] = parent if parent != -1: depth[u] = depth[parent] + 1 for j in range(1, logn): if dp[u][j-1] != -1: dp[u][j] = dp[dp[u][j-1]][j-1] for v in adj_list[u]: if v == parent: continue dfs(v, u) dfs(root) return dp, depth ``` 此部分实现了对输入图结构的遍历以及 `dp` 和 `depth` 数组初始化[^1]。 #### 查询阶段 对于每次询问 `(u,v)` ,先将两者的深度调整一致再逐步提升直到两者相遇: ```python def lca_query(dp, depth, u, v): if depth[u] < depth[v]: u, v = v, u diff = abs(depth[u]-depth[v]) while diff > 0: k = int(math.log2(diff)) u = dp[u][k] diff -= pow(2,k) if u == v: return u for k in reversed(range(len(dp[0]))): if dp[u][k]!=-1 and dp[u][k]!=dp[v][k]: u = dp[u][k] v = dp[v][k] return dp[u][0] ``` 上述代码片段展示了如何基于已有的 `dp` 及 `depth` 数据来快速定位给定两点间的最低共同祖先节点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值