[笔记]: LCA最近公共祖先 2017-06-01 11:38 38人阅读 评论(0) 收藏

LCALeast Common Ancestor),顾名思义,是指在一棵树中,距离两个点最近的两者的公共节点。也就是说,在两个点通往根的道路上,肯定会有公共的节点,我们就是要求找到公共的节点中,深度尽量深的点。

还可以表示成另一种说法,就是如果把树看成是一个图,这找到这两个点中的最短距离。

1.dfs序+RMQLCA

/*
用dfs序和时间戳 
如果求m,n的LCA 记录m,n第一次出现的时间
在时间之内 找到一点有最小深度 此点就是lca 
样例 
5 4
1 2
1 3
2 4
2 5
4 5
*/
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define M 1000
#define N 1000
using namespace std;
int n,m;int num=0;
int b[M],nt[M],p[N];
int a[N],height[N],ttime=0,first[N],minnum[N][N];
void insert(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		num++;
		b[num]=y;
		nt[num]=p[x];
		p[x]=num;

		num++;
		b[num]=x;
		nt[num]=p[y];
		p[y]=num;
	}
}
void dfs(int x,int h){
	++ttime;
	a[ttime]=x;
	height[ttime]=h;
	if(!first[x])
		first[x]=ttime;
	for(int e=p[x];e;e=nt[e])
	{
		int k=b[e];
		if(!first[k]) dfs(k,h+1);
		 ++ttime;
        a[ttime]=x;
        height[ttime]=h;
	}
}
void RMQ()//用RMQ求height中最小的结点  
{//此时minnum中存的是最小的height对应的编号
	for(int i=1;i<=ttime;i++)
		minnum[i][0]=i;
    int t=log2(ttime);
    for(int k=1;k<=t;k++){
        for(int i=1; i<=ttime;i++)
        {
        if(height[minnum[i][k-1]]<=height[minnum[i+(1<<(k-1))][k-1]])//比较左区间最小的height对应的编号对应的height(就是最小的height)与右区间的...... 
           		minnum[i][k]=minnum[i][k-1];
        	else minnum[i][k]=minnum[i+(1<<(k-1))][k-1];//一定是1<<(k-1)不是k!!! 
        }
    }
}
int lca(int x,int y){
	if(x>y) swap(x,y);
	int k;
    int t=log2(ttime);
	if(height[minnum[x][t-1]]<height[minnum[y-t+1][t-1]])
		k=minnum[x][t-1];
	else k=minnum[y-t+1][t-1];
	//printf("k=%d
",k);
	return a[k];
}
int main(){
	insert();
	dfs(1,0);
	RMQ();
	int x,y;
	scanf("%d%d",&x,&y);
	printf("%d
",lca(first[x],first[y]));
	return 0;
}

2.倍增法求LCA(在线做法) 详细见注释

/*
样例:
10 9
1 2
1 3
2 4
2 5
3 6
5 7
5 8
7 9
7 10
9 6
(求9,6的lca) 
思路 两步
1.将深度大的点翻到与深度小的点同一高度
2.两个点一起上翻 找lca 
预处理fa二维数组  d一维数组 
fa[x][y] 表示顶点x向上走2^y个顶点到达的点 d[x]数组是每个点的深度 
*/
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define M 1000
#define N 1000
using namespace std;
int n,m;int num=0;
int b[M],w[M],nt[M],p[N];
int fa[M][20],d[M];
void insert(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		num++;
		b[num]=y;
		nt[num]=p[x];
		p[x]=num;

		num++;
		b[num]=x;
		nt[num]=p[y];
		p[y]=num;
	}
}
void dfs(int x){
	for(int i=1;i<=20;i++){//这里的20视题而定 这里表示最大深度为2^20 
		if(d[x]<(1<<i))break;
		fa[x][i]=fa[fa[x][i-1]][i-1];
	}//求fa数组的关键 和RMQ类似 
	for(int i=p[x];i>0;i=nt[i]){
		int v=b[i];
		if(v==fa[x][0]) continue;//无向边 不走已经走过的父节点 
		fa[v][0]=x;
		d[v]=d[x]+1;//求深度=父节点深度+1 
        dfs(v);
	}
}
int lca(int x,int y){
	int h;
	if(d[x]<d[y]) swap(x,y);
	for(h=0;(1<<h)<=d[x];h++);
	h--;
	for(int i=h;i>=0;i--){
		if(d[x]-(1<<i)>=d[y])
			x=fa[x][i];
	}//使a b深度相等 
	/*
		或者可以写成
		int t=d[x]-d[y];
		for(int i=0;i<=20;i++)
		{
			if(t&(1<<i))
			x=fa[x][i];
		}
		利用位运算 20的解释同上 
	*/
	if(x==y) return x;//如果相等直接return 
	for(int i=h;i>=0;i--){//找lca 从大到小找(图中就是从上往下) lca是最下面的 公共父节点 
		if(fa[x][i]!=fa[y][i]){//关键:如果 不相等 fa[x][i]和fa[y][i]一定在lca的下面一个位置 
			x=fa[x][i];
			y=fa[y][i];
		}
	}
	return fa[x][0];//lca 
}	 
int main(){
	insert();
	dfs(1);
	int x,y;
	scanf("%d%d",&x,&y);
	printf("
LCA=%d
",lca(x,y));
	return 0;
}


3.LCA求最短距离

/*
样例:
10 9
1 2 8
1 3 7
2 4 5
2 5 3
3 6 5
5 7 4 
5 8 4
7 9 3
7 10 2
9 6
(求9,6的最短距离)
思路 求m到n最短距离 
=m到lca的距离+n到lca的距离
=m到根的距离+n到根的距离-2*lca到根的距离 
*/
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define M 1000
#define N 1000
using namespace std;
int n,m;int num=0;
int b[M],w[M],nt[M],p[N];
int fa[M][20],d[M],val[N];
void insert(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		num++;
		b[num]=y;
		w[num]=z;
		nt[num]=p[x];
		p[x]=num;

		num++;
		b[num]=x;
		w[num]=z;
		nt[num]=p[y];
		p[y]=num;
	}
}
void dfs(int x){
	for(int i=1;i<=20;i++){//这里的20视题而定 这里表示最大深度为2^20 
		if(d[x]<(1<<i))break;
		fa[x][i]=fa[fa[x][i-1]][i-1];//求fa数组的关键 和RMQ类似 
	}
	for(int i=p[x];i>0;i=nt[i]){
		int v=b[i];
		if(v==fa[x][0]) continue;//无向边 不走已经走过的父节点 
		fa[v][0]=x;
		d[v]=d[x]+1;//求深度=父节点深度+1 
		val[v]=val[x]+w[i];//求与根结点距离 和深度类似
        dfs(v);
	}
}
int lca(int x,int y){//求LCA 
	int h;
	if(d[x]<d[y]) swap(x,y);
	for(h=0;(1<<h)<=d[x];h++);
	h--;
	for(int i=h;i>=0;i--){
		if(d[x]-(1<<i)>=d[y])
			x=fa[x][i];
	}
	if(x==y) return x;
	for(int i=h;i>=0;i--){
		if(fa[x][i]!=fa[y][i]){
			x=fa[x][i];
			y=fa[y][i];
		}
	}
	return fa[x][0];
}	 
int main(){
	insert();
	dfs(1);
	int m,n;
	scanf("%d%d",&m,&n);
	printf("
LCA=%d
",lca(m,n));
	printf("Distance=%d
",val[m]+val[n]-2*val[lca(m,n)]);
	return 0;
}



原文地址:https://www.cnblogs.com/xljxlj/p/7183633.html