zoukankan      html  css  js  c++  java
  • LCA(最近公共祖先)

    学习链接:https://baike.baidu.com/item/%E4%BC%B8%E5%B1%95%E6%A0%91/7003945?fr=aladdin

    求LCA的方法有很多,在这里就只介绍一种离线算法,Tarjan算法! 可以保证在O(n+q)的时间复杂度内算出所有答案(n是节点个数  q是询问个数)

    为什么叫离线算法呢?  因为这种算法的思想是先把所有的询问存起来,在遍历的树的同时遍历这颗子树包含的询问:

    本算法结合了深度优先搜索(DFS)和并查集的思想,下面说一下大体流程:

      对于新搜索的到的结点,首先要创建由这个节点构成的集合,再对当前节点的每一颗子树进行搜索,每搜索完一颗子树要保证子树内的LCA询问都已解决。其它的LCA必定在

    这个子树之外,这时把子树所形成的的集合与当前节点的集合合并,并把当前节点设为这个集合的祖先 。之后继续搜索下一颗子树,直到当前节点的所有子树搜索完。这时把当前节点也设为已经访问过的结点

    同时处理有关当前节点的询问,如果有一个当前节点到节点v的询问,且被检查过,则由于进行的是深度优先搜索,当前节点与v的最近公共祖先一定还没有被检查过,而这个最近公共祖先的包含v的子树

    一定已经搜索过了,那么这个最近公共祖先一定是v集合所在集合的祖先。

    如图:
    根据实现算法可以看出,只有当某一棵子树全部遍历处理完成后,才将该子树的根节点标记为黑色(初始化是白色),假设程序按上面的树形结构进行遍历,首先从节点1开始,然后递归处理根为2的子树,当子树2处理完毕后,节点2, 5, 6均为黑色;接着要回溯处理3子树,首先被染黑的是节点7(因为节点7作为叶子不用深搜,直接处理),接着节点7就会查看所有询问(7, x)的节点对,假如存在(7, 5),因为节点5已经被染黑,所以就可以断定(7, 5)的最近公共祖先就是find(5).ancestor,即节点1(因为2子树处理完毕后,子树2和节点1进行了union,find(5)返回了合并后的树的根1,此时树根的ancestor的值就是1)。有人会问如果没有(7, 5),而是有(5, 7)询问对怎么处理呢? 我们可以在程序初始化的时候做个技巧,将询问对(a, b)和(b, a)全部存储,这样就能保证完整性。
     
    代码分为六块:
    1、输入树:只需要存x->y有一条边 并不需要存双向边,因为是从根节点开始走的  (也就是入度为0的点) 同时找到根节点
    2、输入询问:注意如果查询u v的最近公共祖先   v u也存进去  (上面已经说明了原因)
    3、初始化并查集
    4、初始化所有点的祖先
    5、初始化所有点都没有被检查过
    6、Tarjan算法:其实就是深搜 再把搜完的子树和当前节点合并为一个集合   祖先是当前节点。  当搜索完所有的子树之后   标记当前节点为访问过   并查询所有与当前节点有关的询问。。
     
    参考代码:
    #include<iostream>
    #include<cstdio>
    #include<vector>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    const int maxn=10000;//最大顶点数
    int n,root;//实际顶点数 树根节点
    int indeg[maxn];//顶点入度  用来判断树根
    vector<int> tree[maxn];//树的邻接表(不一定是二叉树)
    void inputTree()
    {
        scanf("%d",&n);
        for(int i=0;i<n;i++) tree[i].clear(),indeg[i]=0;//初始化树  顶点编号从0开始
        for(int i=1;i<n;i++)//输入n-1条边
        {
            int x,y;
            scanf("%d%d",&x,&y);
            tree[x].push_back(y);//x->y有一条边
            indeg[y]++;//加入邻接表 y入度加一
        }
        for(int i=0;i<n;i++)//寻找树根 入度为0的点
        {
            if(indeg[i]==0)
            {
                root=i;
                break;
            }
        }
    }
    vector<int> query[maxn];//所有查询内容
    void inputQuires()//输入查询
    {
        for(int i=0;i<n;i++)//清空上次查询的内容
            query[i].clear();
        int m;
        scanf("%d",&m);
        while(m--)
        {
            int u,v;
            scanf("%d%d",&u,&v);//查询u和v的lca
            query[u].push_back(v);
            query[v].push_back(u);
        }
    }
    int father[maxn],rnk[maxn];//节点的父亲 秩
    void makeSet()//初始化并查集
    {
        for(int i=0;i<n;i++)
        {
            father[i]=i;
            rnk[i]=0;
        }
    }
    int findSet(int x)//查找祖先
    {
        return father[x]==x?x:father[x]=findSet(father[x]);
    }
    void unionSet(int x,int y)//合并
    {
        x=findSet(x);
        y=findSet(y);
        if(x==y) return;
        if(rnk[x]>rnk[y]) father[y]=x;
        else
        {
            father[x]=y;
            if(rnk[x]==rnk[y]) rnk[y]++;
        }
    }
    int ancestor[maxn];//已经访问节点集合的祖先
    bool vs[maxn];//访问标记
    void Tarjan(int x)//Tarjan算法求解lca
    {
        for(int i=0;i<tree[x].size();i++)
        {
            Tarjan(tree[x][i]);//访问子树
            unionSet(x,tree[x][i]);//将子树节点与根节点x的集合合并
            ancestor[findSet(x)]=x;//合并后的集合的祖先为x
        }
        vs[x]=1;//标记为访问过
        for(int i=0;i<query[x].size();i++)//与根节点有关的查询
        {
            if(vs[query[x][i]]) //如果查询的另一个节点已经访问过 则输出结果
                printf("%d和%d的最近公共祖先为:%d
    ",x,query[x][i],ancestor[findSet(query[x][i])]);
        }
    }
    int main()
    {
        inputTree();//输入树
        inputQuires();//输入查询
        makeSet();
        for(int i=0;i<n;i++) ancestor[i]=i;
        memset(vs,0,sizeof(vs));
        Tarjan(root);
        return 0;
    }
    当初的梦想实现了吗,事到如今只好放弃吗~
  • 相关阅读:
    go.js:画布内容导出为图片
    go.js:拖拽创建流程图
    vue+go.js:实现流程图
    前端:go.js去水印
    screenfull错误
    VUE:axios接收后端文件流并下载文件
    CSS:unset 属性
    上传文件小工具
    个人技能总结2--MyBaties框架
    个人技能总结1:Shiro框架
  • 原文地址:https://www.cnblogs.com/caijiaming/p/10759223.html
Copyright © 2011-2022 走看看