• LCA 最近公共祖先


    LCA ,也就是最近公共祖先是什么意思呢。

    下面这张图可能会让你清楚的明白什么是最近公共祖先。

    对于初始点,前提是它能构成一棵不成环的树,之所以不能成环,从定义就看出来了嘛,如果成环,是不是有种1是3的父亲,3是6的父亲,6又是1的父亲的感觉。O(∩_∩)O哈哈~

    那么我们又从上面一句话能看出另一个问题了,那就是1是6的最远的祖先,3是6最近的祖先是不?

    那么这就引出了最近公共祖先的概念了。

    那么由上图举几个例子。

    13 和11 的最近公共祖先就是6      

    13和2 的最近公共祖先就是1 

    8和6的最近公共祖先就是1

    5和2的最近公共祖先是2

    概念有了,那我们在编程的时候到底怎么去使用呢。

    由于笔者也是刚入门,所以只会一种比较普遍的

    Tarjin (离线算法)

    这是一种比较常见的算法,用到了邻接表这个东西。

    学长给我们当时讲是用vector容器作为邻接表。个人感觉很方便。所以在这顺便介绍一下。会的可以直接跳过。

    ************************************************************************

    所谓邻接表,百度百科给出的定义不免显得很抽象,对于有向图,vi的邻接表中每个表结点都对应于以vi为始点射出的一条边。因此,将有向图的邻接表称为出边表。

    在上面的那张表我们可以清晰的看出1的邻接点为2、3、4;

    6的邻接点为3、10、11。

    那么我们怎么来储存这种关系呢。

    在这里我们用的是vector 容器来储存。

    方式为:vector < int >  vec[n]   ;    //n为节点数,vec[i]就储存了i的邻接点。

    如果需要储存两个参数,比如在查询的时候要按顺序输出,那么我们就可以弄个有两个参数的。

    方式为:vector< pair <int ,int > >   query[n] ;

    那么怎么存入元素呢,很显然是 query[x].push_back({y,i}) ;//y是邻接点,i是输入的序号,x是节点

    那么怎么用呢,方法为:query[x][ j ].first ;//  j是第几个邻接点,这个表示的是y;同理表示i就是 :query[x][ j].second;  

    用法就到这里结束了。(ps:可能是我太菜了。。。)

    *********************************************************************************************************************

    那么邻接表有了,接下来怎么做呢,

    在这里我们假设题目要你查询3、4节点的最近公共祖先和1、4节点的最近公共祖先!

    在这里我们需要用到DFS来进行查找,直到找到根节点

    从上图我们可以发现用dfs查到了3节点。

    我们用Q来表示查询是否有它需要查询的节点已经被查找了。

    如果有那就直接将那个节点的父节点存起来(这里用的是并查集)。

    为什么存父亲节点呢,其实到后面你就发现父亲节点就是最近共祖先!!!

    黄色的数字表示该节点的父节点

    我们清晰的看到1、2、3节点原来的父亲节点是指向自己节点本身的,

    3节点查询之后发现需要查询的节点(也就是4节点)没有被查找过的,

    那么3节点的父亲节点指向2,并且返回2节点

    然后2节点继续向邻接点查询,向4节点查询

    4节点父亲节点是4本身,然后查询,是否有需要查询的节点被访问过了,我们发现3节点被访问过了,那么我们将3节点的父亲节点存入结果中。并发现1节点也已经被访问过了而且父亲节点还是1节点本身,那么我们也把结果存起来。

    查询完了之后,将4节点的父亲节点指向2;并且返回2节点

     

    我们发现没有邻接点可以查了

    然后对2节点查询,发现没有要查询的,于是将2节点的父亲节点指向1,并且返回1节点

    然后对1节点进行查询,发现4节点已经访问过了。

    于是我们将4节点的父亲节点覆盖掉我们之前存的答案,

    但是我们很明显的发现,通过并查集4的父亲节点这时候已经指向1了,所以覆盖并不影响结果

    最后我们就跳出了dfs,并且我们已经完成了所有的查询。

    于是在主函数输出结果就ok,答案应该是

    2

    1

    这就是最近公共祖先的基本求解思路,用到了邻接表dfs并查集

    这种方法也是最常用的算法来求最近公共祖先了,具有一定的普适性。

    ps:可能是我不会其他方法吧,O(∩_∩)O哈哈~

    还有就是上面几张图明显看出纯手画,画的不好,多多包涵哈。

    这就是

    Tarjin (离线算法)的基本思路了。

    如果有不对的地方,望dalao 们在评论区指正,O(∩_∩)O谢谢~

    现在放出一道模板题。附上AC代码;

    POJ-1330  

    Nearest Common Ancestors

    AC 代码如下:

     1 #include<iostream>
     2 #include<stdio.h>
     3 #include<vector>
     4 #include<string.h>
     5 #include<algorithm>
     6 #define N  100005
     7 using namespace std;
     8 int vis[N],pre[N],ans[N];
     9 vector <int> vec[N];
    10 vector <pair <int,int> > query[N];
    11 int Find(int x)
    12 {
    13     if(pre[x]==x)
    14     {
    15         return x;
    16     }
    17     else
    18     {
    19         pre[x]=Find(pre[x]);
    20         return pre[x];
    21     }
    22 }
    23 void dfs(int u,int fa)
    24 {
    25     pre[u]=u;
    26     vis[u]=1;
    27     for(int i=0;i<vec[u].size();i++)
    28     {
    29         int v=vec[u][i];
    30         if(v==fa) continue;//防止走回头路
    31         dfs(v,u);//邻接表查询
    32     }
    33     for(int i=0;i<query[u].size();i++)//查询
    34     {
    35         int v=query[u][i].first;
    36         int id=query[u][i].second;
    37         if(vis[v]==1)
    38         {
    39             ans[id]=Find(v);
    40         }
    41     }
    42     pre[u]=fa;//查询完毕,指向父亲节点。
    43 
    44 }
    45 int main()
    46 {
    47     int T;
    48     cin>>T;
    49     while(T--)
    50     {
    51         memset(vis,0,sizeof(vis));
    52         memset(ans,0,sizeof(ans));
    53         memset(pre,0,sizeof(pre));
    54         int n,x,y;
    55         cin>>n;
    56         for(int i=0;i<n-1;i++)
    57         {
    58             cin>>x>>y;
    59             vec[x].push_back(y);
    60             vec[y].push_back(x);
    61             vis[y]=1;
    62         }
    63         int q;
    64         q=1;
    65         for(int i=0;i<q;i++)
    66         {
    67             cin>>x>>y;
    68             query[x].push_back({y,i});
    69             query[y].push_back({x,i});
    70         }
    71         for(int i=1;i<=n;i++)
    72         {
    73             if(vis[i]==0)
    74             {
    75                 memset(vis,0,sizeof(vis));
    76                 dfs(i,i);
    77                 break;
    78             }
    79         }
    80         for(int i=0;i<q;i++)
    81         {
    82             cout<<ans[i]<<endl;
    83         }
    84         for(int i=1;i<=n;i++)
    85         {
    86             vec[i].clear();
    87             query[i].clear();
    88         }
    89     }
    90     return 0;
    91 }
  • 相关阅读:
    大数据和云计算如何实现视频行业更快速有效的智能分析?
    TSINGSEE青犀视频云边端架构流媒体平台的接口鉴权和接口保活是什么关系?
    TSINGSEE青犀视频和海康合作研发RTMP摄像头如何通过内存卡进行视频录像存储?
    RTMP视频推流功能组件EasyRTMP-HIK DEMO版本运行报错0xc000007b问题排查分析
    互联网视频直播&点播平台RTMP推流组件EasyRTMP如何获取当前推流状态 ?
    程序员修炼之道阅读笔记(一)
    周进度总结
    2020年秋季个人阅读计划
    周进度总结
    Java的Swing布局
  • 原文地址:https://www.cnblogs.com/ISGuXing/p/7219889.html
Copyright © 2020-2023  润新知