lca問題(least common ancestors,最近公共祖先問題),是指給定一棵有根樹t,給出若干個查詢lca(u, v)(通常查詢數量較大),每次求樹t中兩個頂點u和v的最近公共祖先,即找乙個節點,同時是u和v的祖先,並且深度盡可能大(盡可能遠離樹根)。
lca問題有很多解法:線段樹、tarjan演算法、跳表、rmq與lca互相轉化等。本文主要講解tarjan演算法的原理及詳細實現。
lca問題的一般形式:給定一棵有根樹,給出若干個查詢,每個查詢要求指定節點u和v的最近公共祖先。
lca問題有兩類解決思路:
離線演算法,一次性讀入所有查詢,統一進行處理,給出所有答案。
乙個lca的例子如下。比如節點1和6的lca為0。
tarjan演算法是離線演算法,基於後序dfs(深度優先搜尋)和並查集。如果不熟悉並查集,可以檢視並查集及其在最小生成樹中的應用。
演算法從根節點root開始搜尋,每次遞迴搜尋所有的子樹,然後處理跟當前根節點相關的所有查詢。
演算法用集合表示一類節點,這些節點跟集合外的點的lca都一樣,並把這個lca設為這個集合的祖先。當搜尋到節點x時,建立乙個由x本身組成的集合,這個集合的祖先為x自己。然後遞迴搜尋x的所有兒子節點。當乙個子節點搜尋完畢時,把子節點的集合與x節點的集合合併,並把合併後的集合的祖先設為x。因為這棵子樹內的查詢已經處理完,x的其他子樹節點跟這棵子樹節點的lca都是一樣的,都為當前根節點x。所有子樹處理完畢之後,處理當前根節點x相關的查詢。遍歷x的所有查詢,如果查詢的另乙個節點v已經訪問過了,那麼x和v的lca即為v所在集合的祖先。
其中關於集合的操作都是使用並查集高效完成。
演算法的複雜度為,o(n)搜尋所有節點,搜尋每個節點時會遍歷這個節點相關的所有查詢。如果總的查詢個數為m,則總的複雜度為o(n+m)。
比如上面的例子中,前面處理的節點的順序為4->7->5->1->0->…。
當訪問完4之後,集合跟集合合併,得到,並且集合祖先為1。然後訪問7。如果(7,4)是乙個查詢,由於4已訪問過,於是lca(7,4)為4所在集合的祖先,即1。7訪問完之後,把跟合併,得到,祖先為5。然後訪問5。如果(5,7)是乙個查詢,由於7已訪問過,於是lca(5,7)為7所在集合的祖先,即5。如果(5,4)也是乙個查詢,由於4已訪問過,則lca(5,4)為4所在集合的祖先,即1。5訪問完畢之後,把跟合併,得到,並且祖先為1。然後訪問1。如果有(1,4)查詢,則lca(1,4)為4所在集合的祖先,為1。1訪問完之後,把跟合併,得到,祖先為0。然後剩下的2後面的節點處理類似。
接下來提供乙個完整演算法實現。
使用鄰接表方法儲存一棵有根樹。並通過記錄節點入度的方法找出有根樹的根,方便後續處理。
const int mx = 10000; //最大頂點數使用vector陣列query儲存所有的查詢。跟x相關的所有查詢(x,y)都會放在query[x]的陣列中,方便查詢。int n, root; //實際頂點個數,樹根節點
int indeg[mx]; //頂點入度,用來判斷樹根
vectortree[mx]; //樹的鄰接表(不一定是二叉樹)
void inputtree() //輸入樹
for (int i = 0; i < n; i++) //尋找樹根,入度為0的頂點
if (indeg[i] == 0)
}
vectorquery[mx]; //所有查詢的內容然後是並查集的相關資料和操作。void inputquires() //輸入查詢
}
int father[mx], rnk[mx]; //節點的父親、秩再就是tarjan演算法的核心**。void makeset() //初始化並查集
int findset(int x) //查詢
void unionset(int x, int y) //合併
在呼叫tarjan之前已經初始化並查集給每個節點建立了乙個集合,並且把集合的祖先賦值為自己了,因而這裡不用給根節點x單獨建立。
int ancestor[mx]; //已訪問節點集合的祖先下面是主程式,再加乙個樣例輸入輸出作為測試。bool vs[mx]; //訪問標誌
void tarjan(int x) //tarjan演算法求解lca
vs[x] = 1; //標記為已訪問
for (int i = 0; i < query[x].size(); i++) //與根節點x有關的查詢
if (vs[query[x][i]]) //如果查詢的另乙個節點已訪問,則輸出結果
printf("%d和%d的最近公共祖先為:%d\n", x,
query[x][i], ancestor[findset(query[x][i])]);
}
int main()
演算法筆記 LCA問題 tarjan 離線演算法
reference 演算法流程 1 讀入表示父子關係的樹 2 儲存所有詢問 3 一次dfs回答所有詢問 演算法的關鍵是 對於乙個正在訪問中的節點u,其訪問過的子樹加上這個節點本身合併為乙個等價類,等價類有乙個代表元ancestor,值為u。對於u的乙個未訪問過的子節點v,如果屬於詢問 query v...
LCA離線演算法tarjan
lca演算法 lca least common ancestor 是指在一棵樹中,距離兩個點最近的兩者的公共節點。也就是說,在兩個點通往根的道路上,肯定會有公共的節點,我們就是要求找到公共的節點中,深度盡量深的點。還可以表示成另一種說法,就是如果把樹看成是乙個圖,這找到這兩個點中的最短距離。本文先介...
LCA 離線tarjan演算法
對於最近公共祖先問題,我們先來看這樣乙個性質,當兩個節點 u,v 的最近公共祖先是x時,那麼我們可以確定的說,當進行後序遍歷的時候,必然先訪問完x的所有子樹,然後才會返回到x所在的節點。這個性質就是我們使用tarjan演算法解決最近公共祖先問題的核心思想。同時我們會想這個怎麼能夠保證是最近的公共祖先...