【並查集】【樹】最近公共祖先LCA-Tarjan算法

最近公共祖先LCAhtml

雙鏈BT

若是每一個結點都有一個指針指向它的父結點,因而咱們能夠從任何一個結點出發,獲得一個到達樹根結點的單向鏈表。所以這個問題轉換爲兩個單向鏈表的第一個公共結點(先分別遍歷兩個鏈表獲得它們的長度,並求出兩個長度之差。在長的鏈表上先遍歷若干個點以後,再同步遍歷兩個鏈表,知道找到相同的結點,或者一直到鏈表結束)。node

BST

不用遞歸recursive,迭代iterative就行git

public int query(Node t, Node u, Node v) {    
    int left = u.value;    
    int right = v.value;    
    Node parent = null;    

    //二叉查找樹內,若是左結點大於右結點,不對,交換  
    if (left > right) {    
        int temp = left;    
        left = right;    
        right = temp;    
    }    

    while (true) {    
        //若是t小於u、v,往t的右子樹中查找  
        if (t.value < left) {    
            parent = t;    
            t = t.right;    

        //若是t大於u、v,往t的左子樹中查找  
        } else if (t.value > right) {    
            parent = t;    
            t = t.left;    
        } else if (t.value == left || t.value == right) {    
            return parent.value;    
        } else {    
            return t.value;    
        }    
    }    
}

普通BT

單鏈遞歸github

node* getLCA (node* root, node* node1, node* node2)  {  
    if(root == null)  
        return null;  
    if(root== node1 || root==node2)  
        return root;  

    node* left = getLCA(root->left, node1, node2);  
    node* right = getLCA(root->right, node1, node2);  

    if(left != null && right != null)  
        return root;  
    else if(left != null)  
        return left;  
    else if (right != null)  
        return right;  
    else   
        return null;  
}

Tarjan算法

首先,Tarjan算法是一種離線算法,也就是說,它要首先讀入全部的詢問(求一次LCA叫作一次詢問),而後並不必定按
照原來的順序處理這些詢問。而打亂這個順序正是這個算法的巧妙之處。看完下文,你便會發現,若是偏要按原來的順序
處理詢問,Tarjan算法將沒法進行。算法

下面是算法的詳細講解數組

處理結點10的時候並查集的狀況
(假設結點是按從左到右的順序處理的)
與10的LCA是10的結點集合:{10}
與10的LCA是8結點集合:{8 9 11}
與10的LCA是3的結點集合:{3 7}
與10的LCA是1的結點集合:{1 2 5 6}
不屬於任何集合的結點:4 12
  Tarjan算法是利用並查集來實現的。它按DFS的順序遍歷整棵樹。對於每一個結點x,它進行如下幾步操做:
計算當前結點的層號lv[x],並在並查集中創建僅包含x結點的集合,即root[x]:=x。
依次處理與該結點關聯的詢問。
遞歸處理x的全部孩子。
root[x]:=root[father[x]](對於根結點來講,它的父結點能夠任選一個,反正這是最後一步操做了)。
  如今咱們來觀察正在處理與x結點關聯的詢問時並查集的狀況。因爲一個結點處理完畢後,它就被歸到其父結點所在的集合,因此在已經處理過的結點中(包括x自己),x結點自己構成了與x的LCA是x的集合,x結點的父結點及以x的全部已處理的兄弟結點爲根的子樹構成了與x的LCA是father[x]的集合,x結點的父結點的父結點及以x的父結點的全部已處理的兄弟結點爲根的子樹構成了與x的LCA是father[father[x]]的集合……(上面這幾句話若是看着彆扭,就分析一下句子成分,也可參照右面的圖)假設有一個詢問(x,y)(y是已處理的結點),在並查集中查到y所屬集合的根是z,那麼z就是x和y的LCA,x到y的路徑長度就是lv[x]+lv[y]-lv[z]*2。累加全部通過的路徑長度就獲得答案。
  如今還有一個問題:上面提到的詢問(x,y)中,y是已處理過的結點。那麼,若是y還沒有處理怎麼辦?其實很簡單,只要在詢問列表中加入兩個詢問(x,y)、(y,x),那麼就能夠保證這兩個詢問有且僅有一個被處理了(暫時沒法處理的那個就pass掉)。而形如(x,x)的詢問則根本沒必要存儲。
  若是在並查集的實現中使用路徑壓縮等優化措施,一次查詢的複雜度將能夠認爲是常數級的,整個算法也就是線性的了。
  另外,實現上還有一點技巧:樹的邊表和詢問列表的存儲能夠用數組模擬的鏈表來實現。

最近公共祖先LCA Tarjan算法優化

hdu2586 How far away

題意

給定一棵樹,每條邊都有必定的權值,q次詢問,每次詢問某兩點間的距離。指針

解法

這樣就能夠用LCA來解,首先找到u, v 兩點的lca,而後計算一下距離值就能夠了。這裏的計算方法是,記下根結點到任意一點的距離dis[],這樣ans = dis[u] + dis[v] - 2 * dis[lca(v, v)]了,這個表達式仍是比較容易理解的。。code

poj1470 Closest Common Ancestors

這道題和上面那道同樣,很典型的LCA問題,不過讀入有點麻煩,求的是每一個點被做爲最近公共祖先的次數htm

並查集

暢通工程(HDOJ-1232)

題目描述:

某省調查城鎮交通情況,獲得現有城鎮道路統計表,表中列出了每條道路直接連通的城鎮。省政府「暢通工程」的目標是使全省任何兩個城鎮間均可以實現交通(但不必定有直接的道路相連,只要互相間接經過道路可達便可)。問最少還須要建設多少條道路?
  

分析:

若每一個城市之間有且僅有一條道路相連,那這確定是一棵樹了。邊數 = 節點數 -1 ,只要咱們知道城市被分紅的集合數ans,要修的道路就是ans-1 ,下面貼出通過路徑壓縮的並查集。
  

UVA - 10608-Friends

題目大意:

給定n我的,和m個關係,m個關係表明誰和誰認識之類的,求這樣的關係中,朋友圈人數最多的人數。

解題思路:

這題用並查集來判斷這兩我的是否屬於同一個朋友圈,剛開始每一個人本身造成一個朋友圈,因此每一個朋友圈人數爲1,而後每碰到一個關係就判斷這兩我的p1,p2是否屬於同一個朋友圈,若是不是,就把其中一個的f[t](t=find(p1)爲這個圈子的根)改成另外一個朋友圈的根r=find(p2),而後把以r爲根的這個圈子的人數c[r]加上以t爲根的圈子的朋友數c[t]。這樣最後只要找f[i] == i(這樣的i是根)的這樣的圈子的朋友數,找最大的c[i]。

相關文章
相關標籤/搜索