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 }
原文地址:https://www.cnblogs.com/ISGuXing/p/7219889.html