LCA学习笔记

L C A 学 习 笔 记 LCA学习笔记 LCA

在有根树中(包括二叉树和其他各种树),两个节点ta , tb的共同祖先中和两个节点最近的那个被称为最近共同祖先(LCA,Lowest Common Ancestor)
在这里插入图片描述
比如上面这张图中
2和8的LCA是1
7和5的LCA是5
5和6的LCA是3
先从最简单的想法出发去找两个点(w和u)的LCA,也就是一步一步向上寻找,先把深度大的那个节点往上拉,直到两个节点的深度相同,然后两个点同时向上查找,直到第一次两个节点相遇为止,那个点就是w和u的LCA。查询的时间复杂度为O(n),实际上差不多是O( depth(w)+depth(u) ),

比如上图中的2和8号节点,因为8号节点深度更深,所以先把它拉到和2号节点一样深度的位置,也就是3号节点,再同时向上搜,就能找到1是LCA。

对此我们要知道各个节点的深度,和它所对应的父节点,所以初始化创建两个数组记录,用dfs搜索一遍,把数据记录下来就行了。根节点的父节点就设为-1,深度为0。
代码很简单:

vector<int> mp[maxn];		//记录各个点相连的其他点
int parent[maxn];			//记录各个点的父节点
int depth[maxn];			//记录各个点的深度
void dfs(int id,int p,int deep){	//id为当前节点的编号,p为父节点编号,deep为深度
    depth[id] = deep;				
    parent[id] = p;	
    for(int i=0;i<mp[id].size();i++)
        if(mp[id][i]!=p)
            dfs(mp[id][i],deep+1);
}
void init(){
	dfs(root,-1,0);			//root为根节点的编号
}

接下来就是查找LCA,普通的查找也很简单,直接看代码就好了

int LCA(int ta,int tb){								//查询ta和tb两个节点的LCA
	while(depth[ta]>depth[tb]) ta = parent[ta];		
	while(depth[tb]>depth[ta]) tb = parent[tb];		//先拉升到同一个深度
	while(ta!=tb){									//同时向上查找,直到找到LCA
		ta = parent[ta];
		tb = parent[tb];
	}
	return ta;
}

如果查询次数不是很多的话用上面的普通算法还是可以的,当时当查询次数变多了之后,每次都用O( n )来查值就会很慢,有没有什么办法可以快速查询LCA的值呢?显然是有的。
我们可以发现,当w和u的LCA找到之后,再向上查询,(因为w和u已经汇聚到了一个节点上了),所以继续向上查询查到的也都是相同的值,这样我们可以利用二分的思想来找到第一个相同的值,具体如下:
当知道一个点的父节点之后,也就是深度少了20的父节点,我们用它来找到深度减少21的祖先节点,也就是parent2[ x ] = parent[ parent[ x ] ] 。同样的,我们可以找的深度减少22的祖先节点,parent4[ x ] = parent2[ parent2[ x ] ]。这样递推下去就可以得到一张用于查询的表(如果祖先节点不存在就表示为-1)。之后查询的时候利用这张表来进行二分搜索就可以将查询的时间复杂度降低到O(logn),是一个很大的进步。
具体操作就是比朴素的算法稍微复杂了一点,初始化的时候要把所有倍增的节点也计算出来。
具体初始化:

vector<int> mp[maxn];						//记录连接点
int parent[maxn][30];						//第二维只要>log2(maxn)就好了
//parent[i][j]表示编号为i的节点比他深度小了2^j的点的编号
int depth[maxn];							//记录各个点的深度
int maxx = -1;								//下面可以用来优化,用来记录最大的深度
void dfs(int id,int p,int deep){			
	maxx = max(deep,maxx);					
	parent[id][0] = p;
	depth[id] = deep;
	for(int i=0;i<mp[id].size();i++)
		if(mp[id][i]!=p)
			dfs(mp[id][i],id,deep+1);
}
void init(){
	dfs(root,-1,0);							//常规dfs
	for(int i=1;(1<<i)<=n;i++){				//假设一共有n个节点n可改成maxx,和深度有关
		for(int j=1;j<=n;j++){				//把表打出来
			if(parent[j][i-1]<0) parent[j][i] = -1;//祖宗节点不存在,标记为-1
			else parent[j][i] = parent[parent[j][i-1]][i-1];
		}
	}
}

初始化的时候就是会比原来多一个步骤,复杂度变成O(n logn)
接下来就是查询的操作了,单次查询O(log n)

int LCA(int ta,int tb){
	if(depth[ta]<depth[tb]) swap(ta,tb);		//ta为深度根深的
	for(int i=0;depth[ta]!=depth[tb];i++)		//找到相同深度的点
		if((depth[ta]-depth[tb])>>i & 1)
			ta = parent[ta][i];
	if(ta==tb) return ta;				//如果其中一个点是另一个点的祖宗节点,直接返回
	
	for(int i=log2(maxx);i>=0;i--){		//寻找LCA的子节点
		if(parent[ta][i]!=parent[tb][i]){
			ta = parent[ta][i];
			tb = parent[tb][i];
		}
	}
	return parent[ta][0];			//这时parent[ta][0]和parent[tb][0]就是LCA(ta,tb)
}

洛谷的模板题:https://www.luogu.org/problemnew/show/P3379

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值