LCA問題的Tarjan演算法(POJ1330)

2021-07-05 01:25:34 字數 2400 閱讀 4753

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; //最大頂點數

int n, root; //實際頂點個數,樹根節點

int indeg[mx]; //頂點入度,用來判斷樹根

vectortree[mx]; //樹的鄰接表(不一定是二叉樹)

void inputtree() //輸入樹

for (int i = 0; i < n; i++) //尋找樹根,入度為0的頂點

if (indeg[i] == 0)

}

使用vector陣列query儲存所有的查詢。跟x相關的所有查詢(x,y)都會放在query[x]的陣列中,方便查詢。

vectorquery[mx]; //所有查詢的內容

void inputquires() //輸入查詢

}

然後是並查集的相關資料和操作。

int father[mx], rnk[mx]; //節點的父親、秩

void makeset() //初始化並查集

int findset(int x) //查詢

void unionset(int x, int y) //合併

再就是tarjan演算法的核心**。

在呼叫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演算法解決最近公共祖先問題的核心思想。同時我們會想這個怎麼能夠保證是最近的公共祖先...