1. 程式人生 > >最近公共祖先LCA:Tarjan演算法

最近公共祖先LCA:Tarjan演算法

1,並查集+dfs 
對整個樹進行深度優先遍歷,並在遍歷的過程中不斷地把一些目前可能查詢到的並且結果相同的節點用並查集合並. 

2,分類,使每個結點都落到某個類中,到時候只要執行集合查詢,就可以知道結點的LCA了。 
對於一個結點u.類別有: 
以u為根的子樹、除類一以外的以f(u)為根的子樹、除前兩類以外的以f(f(u))為根的子樹、除前三類以外的以f(f(f(u)))為根的子樹…… 

類一的LCA為u,類二為f(u),類三為f(f(u)),類四為f(f(f(u)))。這樣的分類看起來好像並不困難。 
但關鍵是查詢是二維的,並沒有一個確定的u。接下來就是這個演算法的巧妙之處了。 
利用遞迴的LCA過程。 


當lca(u)執行完畢後,以u為根的子樹已經全部併為了一個集合。而一個lca的內部實際上做了的事就是對其子結點,依此呼叫lca. 
當v1(第一個子結點)被lca,正在處理v2的時候,以v1為根的子樹+u同在一個集合裡,f(u)+編號比u小的u的兄弟的子樹 同在一個集合裡,f(f(u)) + 編號比f(u)小的 f(u)的兄弟 的子樹 同在一個集合裡……  
而這些集合,對於v2的LCA都是不同的。因此只要查詢x在哪一個集合裡,就能知道LCA(v2,x) 

還有一種可能,x不在任何集合裡。當他是v2的兒子,v3,v4等子樹或編號比u大的u的兄弟的子樹(等等)時,就會發生這種情況。即還沒有被處理。還沒有處理過的怎麼辦?把一個查詢(x1,x2)往查詢列表裡新增兩次,一次新增到x1的列表裡,一次新增到x2的列表裡,如果在做x1的時候發現 x2已經被處理了,那就接受這個詢問。(兩次中必定只有一次詢問被接受).

  • Source Code
    #include<iostream>
    #include<vector>
    using namespace std;
    
    const int MAX=10001;
    int f[MAX];
    int r[MAX];
    int indegree[MAX];//儲存每個節點的入度
    int visit[MAX];
    vector<int> tree[MAX],Qes[MAX];
    int ancestor[MAX];
    
    
    void init(int n)
    {
        for(int i=1;i<=n;i++)
        {
    
            r[i]=1;
            f[
    i]=i; indegree[i]=0; visit[i]=0; ancestor[i]=0; tree[i].clear(); Qes[i].clear(); } } int find(int n) { if(f[n]==n) return n; else f[n]=find(f[n]); return f[n]; }//查詢函式,並壓縮路徑 int Union(int x,int y) { int a=find(x); int b=find(y); if(a==b) return 0; //相等的話,x向y合併 else if(r[a]<=r[b]) { f[a]=b; r[b]+=r[a]; } else { f[b]=a; r[a]+=r[b]; } return 1; }//合併函式,如果屬於同一分支則返回0,成功合併返回1 void LCA(int u) { ancestor[u]=u; int size = tree[u].size(); for(int i=0;i<size;i++) { LCA(tree[u][i]); Union(u,tree[u][i]); ancestor[find(u)]=u; } visit[u]=1; size = Qes[u].size(); for(int i=0;i<size;i++) { //如果已經訪問了問題節點,就可以返回結果了. if(visit[Qes[u][i]]==1) { cout<<ancestor[find(Qes[u][i])]<<endl; return; } } } int main() { int cnt; int n; cin>>cnt; while(cnt--) { cin>>n;; init(n); int s,t; for(int i=1;i<n;i++) { cin>>s>>t; tree[s].push_back(t); indegree[t]++; } //這裡可以輸入多組詢問 cin>>s>>t; //相當於詢問兩次 Qes[s].push_back(t); Qes[t].push_back(s); for(int i=1;i<=n;i++) { //尋找根節點 if(indegree[i]==0) { LCA(i); break; } } } return 0; }