LCA

lca指的是树上两点的最近公共祖先,也就是说,在这两点的所有祖先中深度最大的就是它们的最近公共祖先。

求解lca的方法一般情况下有三种:向上标记法,树上倍增法,tarjan

此处只介绍前两种方法(因为不会tarjan


 

  • 向上标记法

    假定一棵树有8个节点,如图所示, lca(4,7)=1

  

      例如求4处有一只小兔子A,7处也有一只小兔子B,它们都只能顺着这棵树向上跳

    现在想要知道它们最近可以在什么地方见面

    解决这个问题,其实就是求lca(4,7)

    我们让小兔子A从4向上跳到根节点,并标记所有经过的节点

    同样的,让小兔子B从7向上走到根节点,第一次遇到的被标记的节点就是4和7的最近公共祖先

    时间复杂度最坏为O(n)

  下面重点介绍树上倍增法(因为常用)

  • 树上倍增法

    仍然是上图,求同样的问题

    可以发现4的深度比7的深度深

    那么让小兔子A从4向上跳,直到和小兔子B所在的节点7同一深度时停下来

    此时如果两只小兔子已经会面,那么此时它们所在的点就是lca(4,7)的值

    如果没有会面呢?

    那么就让两只小兔子一起向上跳,直到它们会面为止

    但是有一个问题,如果小兔子A所在的树链特别的长,那它岂不是要跳好久才能和小兔子B到同一高度?

    所以聪明的小兔子就想出了一个办法:每次按2的i次幂跳

    这就用到了倍增

    这样子在数据最坏的情况下两只小兔子也可以很快见面啦

    不妨设f[x][i]表示x向上的第2i个节点

    对于初始化,显然f[x][0]也就表示x的父节点

    对于范围内的任意i,f[x][i]=f[ f[x][i-1] ][i-1]

    除了f数组之外,我们还需要一个d数组记录每个点的深度

    具体如何求解lca(x,y)呢?步骤如下:

      首先找出深度更深的点x(如果y比x深度深就交换)

      然后让x向上倍增直到与y同一深度

      如果此时x与y是同一点,那么就返回x所在位置(返回y所在位置也无所谓)

      如果不是同一点就一起向上倍增,直到是同一点就返回此时点所在的位置

    如果还是有疑问,那么就请参考以下程序理解吧

    另外,我用的是vector存图,当然也可以用链式前向星

vector<int> g[maxn];
void pre(int u,int fa){//初始化 
    d[u]=d[fa]+1;//求深度 
    for(int i=1;i<=15;i++){//i的范围根据题的数据范围而定 
        f[u][i]=f[f[u][i-1]][i-1];
    }
    int sz=g[u].size();//表示u所连接的边数 
    for(int i=0;i<sz;i++){
        if(g[u][i]!=fa){//g[u][i]表示以u为起点所连接的第i条边的终点 
            f[g[u][i]][0]=u;
            pre(g[u][i],u);
        }
    }
}
int lca(int x,int y){
    if(d[x]<d[y]){//让x为深度最深的点 
        swap(x,y);
    }
    for(int i=15;i>=0;i--){//跳到同一深度 
        if(d[f[x][i]]>=d[y]){
            x=f[x][i];
        }
        if(x==y){//如果此时是同一个点就输出 
            return x;
        }
    }
    for(int i=15;i>=0;i--){//让x,y同时向上跳 
        if(d[f[x][i]]!=d[f[y][i]]){//如果不是同一点就更新x,y的值 
            x=f[x][i];             //如果是同一点那么x的父节点就是答案 
            y=f[y][i];
        }
    }
    return f[x][0];//答案就是x的父节点 
}

就这么多啦 欢迎指正

原文地址:https://www.cnblogs.com/duojiaming/p/11298826.html