最近公共祖先LCAhtml
若是每一個結點都有一個指針指向它的父結點,因而咱們能夠從任何一個結點出發,獲得一個到達樹根結點的單向鏈表。所以這個問題轉換爲兩個單向鏈表的第一個公共結點(先分別遍歷兩個鏈表獲得它們的長度,並求出兩個長度之差。在長的鏈表上先遍歷若干個點以後,再同步遍歷兩個鏈表,知道找到相同的結點,或者一直到鏈表結束)。node
不用遞歸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; } } }
單鏈遞歸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算法是一種離線算法,也就是說,它要首先讀入全部的詢問(求一次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)的詢問則根本沒必要存儲。 若是在並查集的實現中使用路徑壓縮等優化措施,一次查詢的複雜度將能夠認爲是常數級的,整個算法也就是線性的了。 另外,實現上還有一點技巧:樹的邊表和詢問列表的存儲能夠用數組模擬的鏈表來實現。
給定一棵樹,每條邊都有必定的權值,q次詢問,每次詢問某兩點間的距離。指針
這樣就能夠用LCA來解,首先找到u, v 兩點的lca,而後計算一下距離值就能夠了。這裏的計算方法是,記下根結點到任意一點的距離dis[],這樣ans = dis[u] + dis[v] - 2 * dis[lca(v, v)]了,這個表達式仍是比較容易理解的。。code
這道題和上面那道同樣,很典型的LCA問題,不過讀入有點麻煩,求的是每一個點被做爲最近公共祖先的次數htm
某省調查城鎮交通情況,獲得現有城鎮道路統計表,表中列出了每條道路直接連通的城鎮。省政府「暢通工程」的目標是使全省任何兩個城鎮間均可以實現交通(但不必定有直接的道路相連,只要互相間接經過道路可達便可)。問最少還須要建設多少條道路?
若每一個城市之間有且僅有一條道路相連,那這確定是一棵樹了。邊數 = 節點數 -1 ,只要咱們知道城市被分紅的集合數ans,要修的道路就是ans-1 ,下面貼出通過路徑壓縮的並查集。
給定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]。