最近公共祖先 : LCA

一.什么是最近公共祖先

在一棵树上,两个节点之间公共的深度最大的祖先节点 称为最近公共祖先,例如:

(1)4和3的最近公共祖先是1

(2)4和2的最近公共祖先是2

(3)4和5的最近公共祖先是2

二.如何求两点间的最近公共祖先

1.离线Tarjan算法(基于DFS + 并查集)

(1)所谓离线算法就是把所有的询问保存下来,在一次搜索中全部处理完,最后一起输出。

(2)DFS深搜的特点是:先进入父节点在进入其子孙节点,利用这个特点,我们对于两个节点的公共祖先节点有两种情况:

(假设两个节点为v,u)

<1>u节点是v节点的直接父节点或者祖先节点,这时LCA(u,v) = u,因为肯定先搜u节点,所以标记u节点,进入v发现u已经被标记,此时LCA(u,v) = u;

<2>u节点跟v节点不是一颗子树里面,那么v可能是u的父亲的另一颗子树里面的,也可能是u的父亲的父亲的另一颗子树里面的。。以此类推,反之亦然。所以u和v的最近公共祖先一定是目前所合并的u或者v的祖先节点pre[v]或者pre[u]。

假设先搜索到u节点,现在寻找v在u的父亲的哪个子树或者u的祖先的哪个子树里面,现在就要回溯回祖先节点搜索下一个子树了。每从u回溯一层,搜这层的其他子树时就是寻找v时,如果这时在其他子树里找到了v,那公共祖先就是pre[pre[pre[.....u] ] ](看回溯层数),也就是并查集不断合并的过程!

也就是回溯到u的着一层祖先节点时,在子树里找到了v,那么最近公共祖先就是这层的节点!

(3)算法实现

<1> 先搜后判断

标记当前节点

搜索所有子节点,合并子节点到当前节点

循环所有跟当前的查询关系,若v被标记,公共祖先就是pre[v];

注:此方法会在<1>情况时,父节点和子节点都求一遍

<2>先判断后搜索

循环所有跟当前的查询关系,若v被标记,公共祖先就是pre[v];

标记当前节点

搜索所有子节点,合并子节点到当前节点

(4)代码实现:

#include <iostream>
//#include<bits/stdc++.h>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 100000 + 7;
struct Edge{//保存树上边
     int to,next;
}edge[maxn];
struct QEdge{//保存查询关系
     int from,to,lca,next;
}Qedge[maxn];
int n,m,head[maxn],Qhead[maxn],tot,cnt,ans[maxn],pre[maxn];
bool judge[maxn],notRoot[maxn];
void addEdge(int a,int b){//树上加边
    edge[tot].to = b;edge[tot].next  = head[a];head[a] = tot++;
}
void addQEdge(int a,int b){//关系增加
   Qedge[cnt].from = a;Qedge[cnt].to = b;Qedge[cnt].next = Qhead[a];Qhead[a] = cnt++;
}
void init(){//初始化
    tot = cnt = 0;
    memset(head,-1,sizeof(head));
    memset(Qhead,-1,sizeof(Qhead));
    memset(judge,0,sizeof(judge));
    memset(notRoot,0,sizeof(notRoot));
    memset(ans,0,sizeof(ans));
    for(int i = 0;i<=n;i++)pre[i] = i;
}
int finded(int x){
    int v = x;
    while(v!=pre[v])v = pre[v];
    int j = x;
    while(j!=v){
        int temp = pre[j];
        pre[j] = v;
        j = temp;
    }
    return v;
}
void Union(int a,int b){//并查集
     int fx = finded(a);
     int fy = finded(b);
     if(fx!=fy){
        pre[fx] = fy;
     }
}
void LCA(int root){
    judge[root] = 1;//标记当前节点
    for(int i = head[root];~i;i = edge[i].next){//搜索子树
        if(!judge[edge[i].to]){
            LCA(edge[i].to);
            Union(edge[i].to,root);//合并节点
        }
    }
    for(int i = Qhead[root];~i;i = Qedge[i].next){//判断所有关系
        if(judge[Qedge[i].to]){//已经被标记
            int fa = finded(Qedge[i].to);//最近公共祖先
            Qhead[i].lca = fa;
            if(i%2==0){//更新另一个相对关系
               Qhead[i+1].lca = fa;
             }
            else Qhead[i-1].lca = fa;
        }
    }
}
int main()
{
    while(scanf("%d",&n)!=EOF){
        init();
        int root;
        for(int i = 1;i<=n;i++){
            int p,len;
            scanf("%d%d",&p,&len);//父节点,子节点数目
            for(int i = 0;i<len;i++){
                int child;
                scanf("%d",&child);//子节点
                notRoot[child] = true;
                addEdge(p,child);
            }
        }
        for(int i = 1;i<=n;i++){
            if(!notRoot[i]){//寻找根节点
                root = i;
                break;
            }
        }
        scanf("%d",&m);//查询数目
        while(m--){
            int x,y;
            scanf("%d%d",&x,&y);
            addQEdge(x,y);//增加关系
            addQEdge(y,x);
        }
        LCA(root);//LCA
        for(int i = 0;i<cnt;i+=2){
           printf("(%d %d) LCA is %d\n",Qedge[i].from,Qedge[i].to,Qedge[i].lca);
        }
    }
    return 0;
}
#include <iostream>
#include<bits/stdc++.h>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 100000 + 7;
struct Edge{
     int to,next;
}edge[maxn];
struct QEdge{
     int from,to,lca,next;
}Qedge[maxn];
int n,m,head[maxn],Qhead[maxn],tot,cnt,ans[maxn],pre[maxn];
bool judge[maxn],notRoot[maxn];
void addEdge(int a,int b){
    edge[tot].to = b;edge[tot].next  = head[a];head[a] = tot++;
}
void addQEdge(int a,int b){
   Qedge[cnt].from = a;Qedge[cnt].to = b;Qedge[cnt].next = Qhead[a];Qhead[a] = cnt++;
}
void init(){
    tot = cnt = 0;
    memset(head,-1,sizeof(head));
    memset(Qhead,-1,sizeof(Qhead));
    memset(judge,0,sizeof(judge));
    memset(notRoot,0,sizeof(notRoot));
    memset(ans,0,sizeof(ans));
    for(int i = 0;i<=n;i++)pre[i] = i;
}
int finded(int x){
    int v = x;
    while(v!=pre[v])v = pre[v];
    int j = x;
    while(j!=v){
        int temp = pre[j];
        pre[j] = v;
        j = temp;
    }
    return v;
}
void Union(int a,int b){
     int fx = finded(a);
     int fy = finded(b);
     if(fx!=fy){
        pre[fx] = fy;
     }
}
void LCA(int root){
     for(int i = Qhead[root];~i;i = Qedge[i].next){
        if(judge[Qedge[i].to]){
            int fa = finded(Qedge[i].to);
            Qedge[i].lca = fa;
            if(i%2==0)Qedge[i+1].lca = fa;
            else Qedge[i-1].lca = fa;
        }
    }
    judge[root] = 1;
    for(int i = head[root];~i;i = edge[i].next){
        if(!judge[edge[i].to]){
            LCA(edge[i].to);
            Union(edge[i].to,root);
        }
    }
}
int main()
{
    while(scanf("%d",&n)!=EOF){
        init();
        int root;
        for(int i = 1;i<=n;i++){
            int p,len;
            scanf("%d%d",&p,&len);
            for(int i = 0;i<len;i++){
                int child;
                scanf("%d",&child);
                notRoot[child] = true;
                addEdge(p,child);
            }
        }
        for(int i = 1;i<=n;i++){
            if(!notRoot[i]){
                root = i;
                break;
            }
        }
        scanf("%d",&m);
        while(m--){
            int x,y;
            scanf("%d%d",&x,&y);
            addQEdge(x,y);
            addQEdge(y,x);
        }
        LCA(root);
        for(int i = 0;i<cnt;i+=2){
           printf("(%d %d) LCA is %d\n",Qedge[i].from,Qedge[i].to,Qedge[i].lca);
        }
    }
    return 0;
}

2.在线算法

待补....

3.LCA应用:

树上两节点最短距离dis(u,v) = dis(u,root) + dis(v,root) - 2*dis(LCA(u,v), root);

树上两节点最短距离 = u到根节点的距离 + v到根节点的距离 - 两倍的公共祖先到根结点的距离

猜你喜欢

转载自blog.csdn.net/qq_40772692/article/details/82960421