首先,介紹何謂“最近公共祖先”,其實就是對于一顆二叉或者多叉樹來說,每個節點都有祖先節點(根節點除外),對于任意兩個點,a,b,它們可能有多個公共的祖先點c,即c為a的祖先且c為b的祖先,我們定義深度最大的那個公共祖先C為a,b的最近公共祖先,這個點是唯一的。
對于求最近公共祖先的算法有不少,著名的是LCA的線上算法和離線算法,看了這麼多網上的代碼,很少能找到我中意的,而且有些線上算法時間複雜度實在太高,是以我覺得有必要把自己想的一些東西拿出來給大家分享分享。
我們想想普通的方法來求a,b兩個點的最近公共祖先C:
View Code
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:
View Code
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))的時候,我們把段标記為它的父節點,這樣深度相同的節點也可以在不同的段裡面,可以讓我們更快的查詢最近公共祖先。(這裡感謝一樓的提醒,一開始忘記說了)
View Code
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即可。
View Code
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 }
轉載于:https://www.cnblogs.com/Free-rein/archive/2012/08/24/2654519.html