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 }