定义:
最近公共祖先简称 LCA(Lowest Common Ancestor)。两个节点的最近公共祖先,就是这两个点的公共祖先里面,离根最远的那个。
性质:
1.;
2. 是的祖先,当且仅当;
3. 如果不为的祖先并且不为的祖先,那么 分别处于的两棵不同子树中;
4. 前序遍历中, 出现在所有 中元素之前,后序遍历中 则出现在所有 中元素之后;
5. 两点的最近公共祖先必定处在树上两点间的最短路上;
6. ,其中 是树上两点间的距离, 代表某点到树根的距离。
算法实现:
1.朴素算法:
让 和 中深度深的点先走 步,然后两个点同时向上走,直到走到同一个节点。
复杂度:
2.倍增(基于二分搜索):
朴素算法的改进算法。
通过预处理 数组,游标可以快速移动,大幅减少了游标跳转次数。 表示点 的第 个祖先。 数组可以通过 预处理出来。
复杂度:
每次询问的复杂度为 ,预处理 数组的复杂度为 。
主要优化:
对于深度不同的两个点 和 ,其深度的差值为 ,然后对其进行二进制拆分,借助 数组,将差值消除,使得两个点的深度相同。
将两个点调整至相同位置之后,与朴素算法相同,两个点同时向上跳。具体实现:从最大可能的 值开始尝试,直到 (包括 )。如果 ,那么 。否则继续尝试。
注意不能从低位到高位继续尝试,会把 跳过。
该技巧在 以外的地方也可使用。
具体代码实现:
const int N=1e4+5;
const int maxk=20;//最大可能k值
vector<int>pic[N];//存图
int depth[N],pre[maxk][N];//点的深度,及预处理的2^k表
int root,n;//根节点,节点数
void dfs(int v,int p,int d)//(当前点,其父亲节点,其深度)
{//处理出各点的深度和父亲
depth[v]=d;
pre[0][v]=p;
for(int i=0;i<pic[v].size();i++)
{
if(pic[v][i]!=p)
dfs(pic[v][i],v,d+1);
}
}
void init()
{
//预处理出depth和pre[0][i];
dfs(root,-1,0);//根节点父亲=-1
//预处理出全部的pre
for(int k=0;k+1<maxk;k++)
{
for(int i=1;i<=n;i++)
{
if(pre[k][i]==-1)
pre[k+1][i]=-1;
else
pre[k+1][i]=pre[k][pre[k][i]];
}
}
}
int lca(int u,int v)
{
if(depth[u]>depth[v])//保持u的深度小于v
swap(u,v);
//消除深度差
for(int k=0;k<maxk;k++)//枚举差值的二进制位,从低到高,从高到低均可
{
if((depth[v]-depth[u])>>k&1)
v=pre[k][v];//深度差-=2^k
}
if(u==v)
return u;
//二分搜索计算lca
for(int k=maxk-1;k>=0;k--)//只能从高到低,从低到高会把lca跳过
{
if(pre[k][u]!=pre[k][v])
{
u=pre[k][u];
v=pre[k][v];
}
}
return pre[0][u];
}
例题:
Nearest Common Ancestors POJ - 1330【模板题】
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
const int N=1e4+5;
vector<int>pic[N];//存图
int depth[N],pre[20][N],degree[N];//点的深度,及预处理的2^k表
int root,maxk=20,n;//根节点,最大可能k值,节点数
void dfs(int v,int p,int d)//处理出各点的深度和父亲
{
depth[v]=d;
pre[0][v]=p;
for(int i=0;i<pic[v].size();i++)
{
if(pic[v][i]!=p)
dfs(pic[v][i],v,d+1);
}
}
void init()
{
//预处理出depth和pre[0][i];
dfs(root,-1,0);
//预处理出全部的pre
for(int k=0;k+1<maxk;k++)
{
for(int i=1;i<=n;i++)
{
if(pre[k][i]==-1)
pre[k+1][i]=-1;
else
pre[k+1][i]=pre[k][pre[k][i]];
}
}
}
int lca(int u,int v)
{
if(depth[u]>depth[v])//保持u的深度小于v
swap(u,v);
//消除深度差
for(int k=0;k<maxk;k++)//枚举差值的二进制位,从低到高,从高到低均可
{
if((depth[v]-depth[u])>>k&1)
v=pre[k][v];//深度差-=2^k
}
if(u==v)
return u;
//二分搜索计算lca
for(int k=maxk-1;k>=0;k--)//只能从高到低,从低到高会把lca跳过
{
if(pre[k][u]!=pre[k][v])
{
u=pre[k][u];
v=pre[k][v];
}
}
return pre[0][u];
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
int u,v;
memset(degree,0,sizeof(degree));
for(int i=1;i<=n;i++)
pic[i].clear();
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
pic[u].push_back(v);
degree[v]++;
}
scanf("%d%d",&u,&v);
for(int i=1;i<=n;i++)
{
if(degree[i]==0)
{
root=i;
break;
}
}
init();
printf("%d\n",lca(u,v));
}
return 0;
}
3.Tarjan算法(离线):
来源:CSDN
作者:1024的孤独
链接:https://blog.csdn.net/weixin_43184669/article/details/104113501