最近公共祖先 LCA 詳解

2021-07-04 20:41:00 字數 2400 閱讀 1692

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 最近公共祖先

直接暴力搜尋參考 普通搜尋每次查詢都需要 樸素演算法是一層一層往上找,倍增的話直接預處理出乙個 具體做法是 維護乙個 的關係來線性求出這個陣列 int anc n 31 int dep n 記錄節點深度 void dfs int u,int parent for int i 0 i g u size...

最近公共祖先 最近公共祖先(LCA)

如題,給定一棵有根多叉樹,請求出指定兩個點直接最近的公共祖先。輸入格式 第一行包含三個正整數n m s,分別表示樹的結點個數 詢問的個數和樹根結點的序號。接下來n 1行每行包含兩個正整數x y,表示x結點和y結點之間有一條直接連線的邊 資料保證可以構成樹 接下來m行每行包含兩個正整數a b,表示詢問...

LCA 最近公共祖先

定義 對於有根樹t的兩個結點u v,最近公共祖先lca t,u,v 表示乙個結點x,滿足x是u v的祖先且x的深度盡可能大。另一種理解方式是把t理解為乙個無向無環圖,而lca t,u,v 即u到v的最短路上深度最小的點。現在給定乙個根為1的樹,求某兩個點的最近公共祖先。思路 預處理出每個點的深度再一...