LCA_倍增法
说起LCA,大家一定不会陌生,最近公共父祖先。
那么求解这种问题,我熟悉的算法有两种:tarjan+并查集离线算法 / 倍增法
今天主要分享一下倍增法,效率是比前一种方法高的。
主要思想:
从深度较深的节点向上移动,直到和另外一个节点处于同一深度。
然后两个节点一起向上移动,直到各自的节点第一次相同时,终止,这个相同的节点就是最近公共父祖先。
那么在倍增法中,是采用跳跃式的,并且跳跃的步数是从大到小,所以如果保证能找到LCA呢。
下面我们通过代码进行详细解释。
所谓倍增,顾名思义成倍增加,那么按多少倍呢? 我们使用2的n 次方倍来操作
首先定义int maxExp = 0,pow = 1;
while(pow<N){
maxExp +=1;
pow *= 2;
}
据次求出树最大能跳2的多少倍(maxExp);
我们定义一个二位数组parents[N][maxExp];用来存储N点的maxExp节点的值;
含义: parents[N][0] 表示N的父节点(2的0次方 =1,N的上一个节点,即父节点),
parents[N][1]表示N的爷爷节点(2的1次方=2,N的上面第二个节点,即爷爷节点),
parents[N][2]表示N的爷爷的爷爷节点(2的2次方=4,N的上面第4个节点,即爷爷的爷爷节点)
以此类推。。。。。
不难看出 parents[N][2] = 爷爷的爷爷节点 = parents[parents[N][1]][1],(红色表示N的爷爷)
由此可推出parents[N][i] = parents[parents[N][i-1]][i-1];
有了这个公式,通过parents数组我们就可以进行倍增存值。不需要存储每个点的每一层,只需要存储倍增节点,我们就能得到所有节点的所有祖先。是不是很神奇?!
这个公式也是倍增法的精髓所在。
好了,进入正文:
通过bfs求出树的每个节点的深度。
伪代码:
depth[N+1];
depth[root] = 1;
bfs(int root,int pre)//根节点和父节点,初始为1,0
{
pq.add([root,pre]);
while(!pq.isEmpty()){
int poll[] = pq.poll;
int u = poll[0];
int fa = poll[1];
//循环为u点赋值,写在这里的原因是从数的根节点,按层级赋值,保证了在为某个节点赋值时,它的祖先节点一定已经赋过值了。
for up->1 to maxExp
{
parents[u][up] = parents[parents[u][up-1]][up-1];
}
//循环u链接的节点
for(arr[u] i->1 to n){
int v = arr[u].get[i];
v==fa : continue;//如果是父节点,跳过
depth[v] = depth[u]+1;//子节点深度为父节点深度+1;
parents[v][0] = u;子节点v的父节点为u
pq.add([v,u]),将新的节点加入队列
}
}
}
当然这里也可以用dfs来实现,原理是一样的。
经过一番操作,我们得到了每个节点的深度,和parents数据的初始化值
那么如何求LCA呢,且看下面分析:
LCA(int a,int b)//求a点和b点的LCA
{
//我们假设a点为深度更深的点,即一会先让a向上跳跃,直到和b在同一层
if(depth[a]<depth[b]){
a = a^b;
b = a^b;
a = a^b;
//将a,b交换位置,保证a点为深度更深的点
}
//让a点先向上跳
for up from maxExp to 0{
//如果a点的祖先节点深度<=b点的深度,那么将a的该祖先节点,赋给a,即a跳到了和b同一层
if(depth[parents[a][up]]<=depth[b]){
a = parents[a][up]
}
}
if a == b return a; 如果此时a=b,说明b是a的LCA;
//否则,a和b同时跳。很多人会问,a,b是从大往小跳,那么如何保证a,b跳到的是最近的公共祖先呢。这里我们使用一个技巧。
我们让a,b同时跳,不去找祖先相同的点,这样我们无法判断是不是最近的。我们可以找第一次不相同的点。因为从上到下,第一次不相同时的点,该点的父节点就是LCA。
这样就比较容易实现了
for up from maxExp to 0{
//该判断表示,找到相等前的那一次不相同。
if(parents[a][up]!=parents[b][up]){
a = parents[a][up];
b = parents[b][up];
}
}
此时LCA为a或b的父节点
return parents[a][0]或者parents[b][0]
}
相信通过代码和解释,大家应该能理解个七七八八吧。要想深入理解,需要做做题,自己写一些,练一练。
我觉得代码都是在使用中逐渐理解透彻的。
bye~
浙公网安备 33010602011771号