tarjan算法求LCA


tarjan算法求LCA

LCA(Least Common Ancestors)的意思是最近公共祖先,即在一棵樹中,找出兩節點最近的公共祖先。

這里我們使用tarjan算法離線算法解決這個問題。

離線算法,是指首先讀入所有的詢問(求一次LCA叫做一次詢問),然后重新組織查詢處理順序以便得到更高效的處理方法。Tarjan算法是一個常見的用於解決LCA問題的離線算法,它結合了深度優先遍歷和並查集,整個算法為線性處理時間。

總思路就是每進入一個節點u的深搜,就把整個樹的一部分看作以節點u為根節點的小樹,再搜索其他的節點。每搜索完一個點后,如果該點和另一個已搜索完點為需要查詢LCA的點,則這兩點的LCA為另一個點的現在的祖先。

1.先建立兩個鏈表,一個為樹的各條邊,另一個是需要查詢最近公共祖先的兩節點。

2.建好后,從根節點開始進行一遍深搜。

3.先把該節點u的father設為他自己(也就是只看大樹的一部分,把那一部分看作是一棵樹),搜索與此節點相連的所有點v,如果點v沒被搜索過,則進入點v的深搜,深搜完后把點v的father設為點u。

4.深搜完一點u后,開始判斷節點u與另一節點v是否滿足求LCA的條件,滿足則將結果存入數組中。

5.搜索完所有點,自動退出初始的第一個深搜,輸出結果。

 

如上圖,根據實現算法可以看出,只有當某一棵子樹全部遍歷處理完成后,才將該子樹的根節點標記為有顏色(初始化是白色),假設程序按上面的樹形結構進行遍歷,首先從節點1開始,然后遞歸處理根為2的子樹,當子樹2處理完畢后,節點2, 5, 6均為紅色;接着要回溯處理3子樹,首先被染色的是節點7(因為節點7作為葉子不用深搜,直接處理),接着節點7就會查看所有詢問(7, x)的節點對,假如存在(7, 5),因為節點5已經被染黑,所以就可以斷定(7, 5)的最近公共祖先就是find(5),即節點1(因為2子樹處理完畢后,子樹2和節點1返回了合並后的樹的根1,此時樹根的祖先的值就是1)。

代碼實現:

 1 #include<cstdio>
 2 #define N 420000
 3 struct hehe{
 4     int next;
 5     int to;
 6     int lca;
 7 };
 8 hehe edge[N];//樹的鏈表
 9 hehe qedge[N];//需要查詢LCA的兩節點的鏈表
10 int n,m,p,x,y;
11 int num_edge,num_qedge,head[N],qhead[N];
12 int father[N];
13 int visit[N];//判斷是否被找過 
14 void add_edge(int from,int to){//建立樹的鏈表 
15     edge[++num_edge].next=head[from];
16     edge[num_edge].to=to;
17     head[from]=num_edge;
18 }
19 void add_qedge(int from,int to){//建立需要查詢LCA的兩節點的鏈表 
20     qedge[++num_qedge].next=qhead[from];
21     qedge[num_qedge].to=to;
22     qhead[from]=num_qedge;
23 }
24 int find(int z){//找爹函數 
25     if(father[z]!=z)
26         father[z]=find(father[z]);
27     return father[z];
28 }
29 int dfs(int x){//把整棵樹的一部分看作以節點x為根節點的小樹 
30     father[x]=x;//由於節點x被看作是根節點,所以把x的father設為它自己 
31     visit[x]=1;//標記為已被搜索過 
32     for(int k=head[x];k;k=edge[k].next)//遍歷所有與x相連的節點 
33         if(!visit[edge[k].to]){//若未被搜索 
34             dfs(edge[k].to);//以該節點為根節點搞小樹 
35             father[edge[k].to]=x;//把x的孩子節點的father重新設為x 
36         }
37     for(int k=qhead[x];k;k=qedge[k].next)//搜索包含節點x的所有詢問 
38         if(visit[qedge[k].to]){//如果另一節點已被搜索過 
39             qedge[k].lca=find(qedge[k].to);//把另一節點的祖先設為這兩個節點的最近公共祖先 
40             if(k%2)//由於將每一組查詢變為兩組,所以2n-1和2n的結果是一樣的 
41                 qedge[k+1].lca=qedge[k].lca;
42             else
43                 qedge[k-1].lca=qedge[k].lca;
44         }
45 }
46 int main(){
47     scanf("%d%d%d",&n,&m,&p);//輸入節點數,查詢數和根節點 
48     for(int i=1;i<n;++i){
49         scanf("%d%d",&x,&y);//輸入每條邊 
50         add_edge(x,y);
51         add_edge(y,x);
52     }
53     for(int i=1;i<=m;++i){
54         scanf("%d%d",&x,&y);//輸入每次查詢,考慮(u,v)時若查找到u但v未被查找,所以將(u,v)(v,u)全部記錄 
55         add_qedge(x,y);
56         add_qedge(y,x);
57     }
58     dfs(p);//進入以p為根節點的樹的深搜 
59     for(int i=1;i<=m;i++)
60         printf("%d ",qedge[i*2].lca);//兩者結果一樣,只輸出一組即可 
61     return 0;
62 }
View Code

 時間復雜度:O(n+m)。

離線算法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM