
最近公共祖先(LCA)

问题场景
给定一颗无向树,并且给出树中任意两个结点,要求求出这两个节点的最近公共祖先。
倍增法
1.DFS初始化
这里直接说解决方法,推荐使用倍增法,原理和跳表(Skiplist)大致相似。原理是使用一个数组,father[i][j]
,含义是结点i的第
(1)父节点处理
和跳表类似,其实对于每个节点,我们开个大小为31的father[i][31]
一般就够用了,其实使用幂乘运算很显然,$2j=2{j-1}\times2{j-1}
1 | for(int i=1;i<31;i++){ |
(2)计算每个节点的深度
不过只知道每个节点的父节点还不够,如果要求LCA,还需要知道每个节点相对于根节点的深度depth[i]
。这个简单,只需要在dfs的时候记录一下就可以了。
2.调整深度寻找LCA
(1)求解深度差,将两个结点置于同一深度
这里的做法是首先求出深度差,比如差值为7,那么转成二进制就是00000111
,那么对于较深的那个节点a,有以下几条步骤:
- 第一步,走一步,找到a的父节点b;
- 第二步,走两步,找到b的第
个父节点c; - 第三步,走四步,找到c的第
个父节点d。
这样一来,最终找到的节点d与a的深度差一定是:。目标达成。
(2)遍历父节点,直到两个结点变成LCA的两个不同儿子
做完第一步有两种情况,情况1是我们刚好找到了LCA,如下图:
这种情况就非常幸运了,直接返回这个节点就可以了。
不过,情况2更为常见,就是我们找到的x和y其实是深度相同的不同节点。
那么这种情况下,还是需要继续遍历的,此时的a就是刚才find的那个‘7’。我们直接从最大的j=30
开始遍历,那么因为这个数字很大,所以一开始father[a][30]和father[b][30]一定都是相同的。
不断减小j,如果减小到一定程度,搞好father[a][j]和father[b][j]不相同,那么就可以进一步缩小范围,更新 a=father[a][j]
,b=father[b][j]
。直接进一步减少j,直到找到最接近LCA的两个不同的a和b。
Question:为什么这里第一次找到不同的father[a][j]和father[b][j]时不能break?
这里非常推荐使用二进制的方式回答这个问题。我们可以回顾求深度差的那个部分,现实中非常有可能出现的情况是,LCA和a的深度差并不是2的整数幂!!!
比如LCA距离a的深度差为23,化成二进制就是10101
,如果只跳一次就是16,就是10000
,所以还是需要继续寻找更小的j的,这里不能就在第一次的时候就break!!
完整代码
1 |
|
- 标题: 最近公共祖先(LCA)
- 作者: fanz
- 创建于 : 2024-11-25 17:42:59
- 更新于 : 2025-04-10 11:19:43
- 链接: https://redefine.ohevan.com/sni2zo/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。