LCA 最近公共祖先
首先,介绍何谓“最近公共祖先”,其实就是对于一颗二叉或者多叉树来说,每个节点都有祖先节点(根节点除外),对于任意两个点,a,b,它们可能有多个公共的祖先点c,即c为a的祖先且c为b的祖先,我们定义深度最大的那个公共祖先C为a,b的最近公共祖先,这个点是唯一的。
对于求最近公共祖先的算法有不少,著名的是LCA的在线算法和离线算法,看了这么多网上的代码,很少能找到我中意的,而且有些在线算法时间复杂度实在太高,因此我觉得有必要把自己想的一些东西拿出来给大家分享分享。
我们想想普通的方法来求a,b两个点的最近公共祖先C:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
int LCA(int a,int b)//求a,b的LCA { while(a!=b)//找到a,b的LCA { if(p[a].deep>p[b].deep) { a=p[a].father; } else { b=p[b].father; } } return a; }
意思就是一步一步向上寻找a,b点的父节点,知道找到他们的父节点相同,那么此时的点就是点C;但是如果这棵树很大,那么这个算法实在太慢了,因此我们就优化这个地方。
我们来看看这样一棵树:
0 -------deep=0
/ \
1 2 -------deep=1
/ \
3 4 -------deep=2
/ \
5 6 ------deep=3
\
7 -------deep=4
我们以深度为界限把数分段,比如我把深度为0和1的点作为第一段,深度2和3的点作为第二段,然后查看a和b是否在同一段,若不是同一段,则向上一段去寻找,直到a和b为同一段,然后再依照父节点去寻找最近公共祖先C:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
int LCA(int a,int b)//求a,b的LCA { while(p[a].sec!=p[b].sec)//找到a,b亮点所在的段 { if(p[a].deep>p[b].deep) { a=p[a].sec; } else { b=p[b].sec; } } while(a!=b)//找到a,b的LCA { if(p[a].deep>p[b].deep) { a=p[a].father; } else { b=p[b].father; } } return a; }
代码中p[u].sec为点u所归属的段,也就是归属的集合,也就是我们常说的并查集来分段。具体怎么分,方法有很多,可以以一个分叉点到下一个分叉点之间的所有点作为一个集合,而且网上大多数算法都是这么做的,这样做起来最好的时间复杂度是o(lgn),但是最复杂的情况将是o(n)。在这里我介绍一种特殊的分段方法,最好和最复杂的时间复杂度都是o(sqrt(n)),即依照节点的深度deep来分段,每一段的长度为sqrt(max(deep)),比如一棵树最大深度为100,那么深度为1~9的点为集合0,深度10~19的点为集合1,依次类推。
补充:
当然如果我们这样来分段,在下面的代码中:
p[u].deep%sec==0;时,我们应该把p[u].sec=p[p[u].father].sec+1;但是这样子的复杂度还是有点高,为了让复杂度再降低,我们把段分的更多,也就是当p[u].deep>=sqrt(max(deep))的时候,我们把段标记为它的父节点,这样深度相同的节点也可以在不同的段里面,可以让我们更快的查询最近公共祖先。(这里感谢一楼的提醒,一开始忘记说了)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void setsection(int u,int sec)//构建点u属于的段集合,每一段深度为sec { if(p[u].deep<sec) { p[u].sec=0; } else { if(p[u].deep%sec==0) { p[u].sec=p[u].father; } else { p[u].sec=p[p[u].father].sec; } } for(unsigned i=0;i<p[u].next.size();i++) { int k=p[u].next[i]; if(p[k].father==u) { setsection(k,sec); } } }
说到这里,整个LCA的算法也就差不多讲完了,并查集分段的方法无外乎这两种,我的代码是后者,毕竟还是比较好理解的,如果不懂也可以留言,下面我把全部的代码都贴上来。
我们一开始给的是一个图,但是我们要把这个图变为一棵树,以任意节点作为根节点建树(此处以点0为root),建树过程标记父节点和深度,然后给节点分段,最后可以做任意次数的查询:
步骤:1.为图建树,标记节点的父节点和深度
2.为树上的节点分段,使用并查集
3.直接查询(a,b)两个节点的LCA即可。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //==================================================================== 2 //Name :LCA最近公共祖先 3 //Author :hxf 4 //copyright :http://www.cnblogs.com/Free-rein/ 5 //Description: 6 //Data :2012.8.20 7 //======================================================================== 8 #include<iostream> 9 #include<algorithm> 10 #include<stdio.h> 11 #include<math.h> 12 #include<string> 13 #include<cstring> 14 #include<vector> 15 #include<stack> 16 #include<queue> 17 #define MAXN 1040 18 #define inf 10100 19 #define pi 3.141592653589793239 20 using namespace std; 21 struct Tree{ 22 vector<int> next;//子节点 23 int father;//父节点 24 int deep;//深度 25 int sec;//属于的段 26 }p[50050]; 27 int visit[50050]; 28 int maxdeep;//最大深度 29 void dfs(int u)//构建多叉树,找到父节点和深度 30 { 31 visit[u]=1; 32 for(unsigned i=0;i<p[u].next.size();i++) 33 { 34 int k=p[u].next[i]; 35 if(visit[k]==0) 36 { 37 p[k].deep=p[u].deep+1; 38 p[k].father=u; 39 dfs(k); 40 } 41 } 42 maxdeep=max(maxdeep,p[u].deep); 43 } 44 void setsection(int u,int sec)//构建点u属于的段集合,每一段深度为sec 45 { 46 if(p[u].deep<sec) 47 { 48 p[u].sec=0; 49 } 50 else 51 { 52 if(p[u].deep%sec==0) 53 { 54 p[u].sec=p[u].father; 55 } 56 else 57 { 58 p[u].sec=p[p[u].father].sec; 59 } 60 } 61 for(unsigned i=0;i<p[u].next.size();i++) 62 { 63 int k=p[u].next[i]; 64 if(p[k].father==u) 65 { 66 setsection(k,sec); 67 } 68 } 69 } 70 void preLCA() 71 { 72 maxdeep=0; 73 dfs(0); 74 setsection(0,(int)sqrt(maxdeep));//每一段深度为sqrt(maxdeep) 75 } 76 int LCA(int a,int b)//求a,b的LCA 77 { 78 while(p[a].sec!=p[b].sec)//找到a,b亮点所在的段 79 { 80 if(p[a].deep>p[b].deep) 81 { 82 a=p[a].sec; 83 } 84 else 85 { 86 b=p[b].sec; 87 } 88 } 89 while(a!=b)//找到a,b的LCA 90 { 91 if(p[a].deep>p[b].deep) 92 { 93 a=p[a].father; 94 } 95 else 96 { 97 b=p[b].father; 98 } 99 } 100 return a; 101 } 102 int main() 103 { 104 int n;//n个节点 105 while(scanf("%d",&n)!=EOF) 106 { 107 for(int i=0;i<n;i++) 108 { 109 p[i].next.clear(); 110 p[i].father=0; 111 p[i].deep=0; 112 p[i].sec=0; 113 } 114 for(int i=0;i<n-1;i++) 115 { 116 int x,y; 117 scanf("%d %d",&x,&y); 118 p[x].next.push_back(y); 119 p[y].next.push_back(x);//建边 120 } 121 memset(visit,0,sizeof(visit)); 122 preLCA();//建树 123 /////////////////////// 124 int m;//做m次查询 125 scanf("%d",&m); 126 while(m--) 127 { 128 int a,b; 129 scanf("%d %d",&a,&b); 130 int lcaab=LCA(a,b);//得到a,b的LCA 131 printf("%d\n",lcaab); 132 } 133 } 134 return 0; 135 }