LCA最近公共祖先演算法

2021-07-09 20:18:06 字數 3419 閱讀 2510

在有根數中,兩個節點u和v的公共祖先中距離最近的那個被稱為最近公共祖先(lca,lowerst common ancestor)。用於搞笑計算lca的演算法有許多,在此我們介紹其中的兩種。在下文中,我們都假設節點數為n。

1.基於二分搜尋的演算法

記節點v到根的深度為depth(v)。那麼,如果節點w是u和v的公共祖先的話,讓u向上走depth(u)-depth(w)步,讓v向上走depth(v)-depth(w)步,就都將走到w。因此,首先讓u和v中較深的一方向上走|depth(u)-depth(v)|步,再一起一步一步向上走,知道走到同乙個節點,就可以在o(depth(u)+depth(v))時間內求出

lca。

const

int max_v=100;

vector

g[max_v];//圖的鄰接表

int root;//根節點編號

int parent[max_v];//父親節點(根節點的父親節點記為-1)

int depth[max_v];//節點深度

void dfs(int v,int p,int d)//當前節點v,v的父親節點,v節點所在深度

}void init()

//計算u和v的lca

int lca(int u,int v)

return u;

}

節點的最大深度是o(n),所以該演算法的時間複雜度也是o(n)。如果只需計算一次lca的話,這便足夠了。但是如果要計算多對節點的lca的話如何是好呢?剛才的演算法,通過不斷向上走到同一節點來計算u和v的lca。這裡,到達同一節點後,無論怎麼向上走,到達的顯然還是同一節點。利用這一點,我們就能夠利用二分搜尋求出到達公共祖先所需要的最少步數嗎?事實上,只要利用如下預處理,就能實現二分搜尋。

首先,對於任意頂點v,利用其父親節點資訊,可以用過parent2[v]=parent[parent[v]]得到其向上走兩步所到的頂點,在利用這一資訊,又可以通過parent4[v]=parent2[parent2[v]]得到向上走4步所到的頂點。以此類推,就能夠得到其向上走2^k步索道的頂點parent[k][v]。有了k=floor(log n)以內的所有資訊後,就可以二分搜尋了,每次的複雜度是o(log n)。另外,預處理parent[k][v]的複雜度是o(nlog n)。

上面的方法也叫調表法。

const int max_v=100;

const int max_log_v=10;

vector

g[max_v];//圖的鄰接表

int root;//根節點編號

intparent[max_log_v][max_v];//向上走2^k步所到的節點(超過根時記為-1)

int depth[max_v];//節點深度

void dfs(int v,int p,int d)//當前節點v,v的父親節點,v節點所在深度

}void init(int v)

}}//計算u和v的lca

int lca(int u,int v)

if(u==v)

return u;

for(int k = max_log_v-1;k>=0;k--)

if(parent[k][u]!=parent[k][v])//利用二分搜尋計算lca,其實就是從上往下找根節點

return

parent[0][u];

}

tarjan離線演算法:具體講解過程見此文,此文**並查集用秩優化,已經遍歷的根節點用ancestor儲存。

下面是乙個朋友寫的具體理解,挺不錯的。

#include 

using

namespace

std;

//fstream input,output;

const

int n=40005;

vector

v[n];

vector

query[n],num[n];

int ans[n],dis[n],father[n];

bool vis[n];

void init(int n)

}int find(int x)

void union(int x,int y)

void tarjan(int o)

for(int i=0;iint tmp = query[o][i];

if(vis[tmp])

printf("the root of %d and %d is %d\n",o,tmp,find(tmp));}}

基於rmq的演算法,將樹轉為從根dfs標號後得到的序列處理的技巧常常十分有效。對於lca,利用該技巧也能夠高效地計算。首先,按照從根dfs訪問的順序得到頂點序列vs[i]和對應的深度depth[i]。對於每個頂點v,記錄其在vs中首次出現的下表為id[v]。

對應的樹:

這些都可以在o(n)時間內求得。而lca(u,v)就是訪問u之後到訪問v之間所經過頂點中離根最近的那個,假設id[u]<=id[v],那麼有

lca(u,v)=vs[id[u]<=i<=id[v]中令depth(i)最小的i]

而這可以利用rmq高效的求得。

#include 

using

namespace

std;

const

int max_v=1000;

int root;

int d[10000][20],mark[10000][20];//記錄最小值和下標

vector

g[max_v];

int vs[max_v*2-1];//dfs訪問順序

int depth[max_v*2-1];//節點的深度

int id[max_v];//各個頂點在vs中首次出現的下標

void rmq_init(const

vector

& a)//rmq查詢區間最小值的**

for(int j=1;(1

<1

// d[i][j]=min(d[i][j-1],d[i+(1<

if(d[i][j-1]1

<

else

}}int query(int l,int r)//返回下標

void dfs(int v,int p,int d,int &k)//節點,父節點,深度,遍歷下標

}}void init(int v)//初始化,用來記錄節點深度和第一次遍歷的節點下標等等

int lca(int u,int v)查詢

其中rmq的查詢還可以用線段樹來代替。

最近公共祖先 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的樹,求某兩個點的最近公共祖先。思路 預處理出每個點的深度再一...