1. 程式人生 > >淺談LCA的幾種演算法

淺談LCA的幾種演算法

LCA,Lowest Common Ancestor,最近的公共祖先。在一棵樹中對於兩個節點u , v找出節點T,使得T同時為uv的祖先。顯然這樣的T點肯定存在且有可能有多個,其中深度最大的那個點肯定為即為uv兩點的LCA。關於LCA的解法有很多種,暴力列舉,事先需要知道所有詢問的離線的tarjan演算法和基於RMQ的線上演算法,下面說一下自己對這種幾種演算法的理解。

⒈最容易想到的暴力搜尋。

 給出節點u , v,,首先對u進行回溯一直到根節點,並對途中的節點加上標記。然後對v進行回溯,直到找到一個被標記的節點T,此時T即為uvLCA。此方法寫起來很簡單但時間複雜度太高,故只適合查詢次數極少的時候。

⒉離線的Tarjan演算法。

此種演算法需要預先知道所有的詢問,並對詢問進行一些預處理,即將有相同節點的詢問放在一塊。演算法的主要思想就是在DFS過程中處理一些資訊從而得到答案。下面說一下我自己的理解。

DFS進行之前,把每個點都看作一個獨立的點集且作為對應點集的代表元。在DFS過程中每次遍歷完一棵子樹,回溯到當前子樹的根節點時,便將這棵子樹上的所有點併到一個點集裡,根節點即為該集合的代表元。然後對與此根節點相關的詢問進行回答。設此節點為u,另一點為v,若v已經完成DFS,則v所在點集的代表元即為uvLCA

下面說一下細節:1.點集的標記可以用並查集來完成。2.每次詢問都查詢了兩次,並且有且只有一次做出回答。

3.此演算法的缺點在於必須知道預先知道所有的詢問,不夠靈活。

⒊基於RMQ的線上查詢演算法。

該演算法藉助於DFS時的訪問次序和RMQ的快速查詢。

depth[]記錄每個點的深度,r[]記錄每個點在DFS過程中第一次被訪問的次序。

劉汝佳的黑書上對此演算法作了詳細的介紹,當時讀的時候有一句“由於每條邊被訪問了兩次,因此一共記錄了2n-1個節點”始終想不明白,後來發現在DFS的遞迴和回溯過程中,對於一個出度為X的點,肯定會被訪問X+1次,1次在遞迴過程中,X在回溯過程中。又因為在一棵樹中除根節點外,每個節點的入度均為1,故入度總和為n-1,又有入度 == 出度,所有所有的節點一共被訪問了n+n-1

次,n次在遞迴過程中,n-1次在回溯過程中。

r[u] < r[v] , point[2n-1]裡面存放了DFS過程中一次被訪問的節點。則在point[ r[u] ]到 point[ r[v] ](包括兩端點)之間深度最小的那個點即為兩點的LCA,深度最小的點有且僅有一個。此時可分為兩種情況,一種是u即為uvLCA,第二種就是第三點wuvLCA

第一種情況中顯然v在一棵以u為根節點的子樹上,此時顯然在DFS過程中先u先放入point[],然後在DFS遍歷這個子樹的過程中v放入point[],所以在[ r[u] , r[v] ] 中 u即為深度最小的那個點。

第二種情況中DFS過程中必先會遍歷完其中一個點所在的子樹,然後會回溯到某一節點w繼續DFS遍歷該節點的其他子樹,如果v在此時遍歷的子樹上,則w即為uvLCA。由上可知,point[][ r[u] , r[v] ] 區間內必有w

區間內的快速查詢可以用到RMQ或者線段樹,這裡就不再贅述。

以上就是我對LCA的幾種演算法的理解,感覺說的有點羅嗦,不足之處還望指出。