LCA的離線演算法

2021-07-24 23:03:59 字數 3358 閱讀 7624

lca(least common ancestor),

顧名思義,是指在一棵樹中,距離兩個點最近的兩者的公共節點。也就是說,在兩個點通往根的道路上,肯定會有公共的節點,我們就是要求找到公共的節點中深度盡量深的點。還可以表示成另一種說法,就是如果把樹看成是乙個圖,這找到這兩個點中的最短距離。

tarjan作為離線off-line演算法,在程式開始前

,需要將所有等待詢問的節點對提前儲存(非常重要,也是為什麼稱之為「離線」的原因),然後程式從樹根開始執行lca()。

如圖:

下面我將詳細解釋lca一般用法:

(1)建立樹的表示

程式設計中,用vectornode[n]來儲存樹的結構

for(i=0;i

上圖執行完這段程式後,結果如圖所示:

紅色的數字是node[s].size不為0的大小.(整棵樹的表示有了哦),是計算出來的哈,寫上去是為了便於大家理解。

(2)儲存查詢,用vectorque[n]來儲存所要的查詢對。

querynum=某個數;

while(querynum!=0)

如:分別輸入(1,2),(2,4),(2,3),(2,7),(7,8),(5,6)等等等等,也就是你可以輸入任意一對樹上的節點,你需要查詢它們的最近公共祖先。

執行後,que如圖下圖所示:

注意:紅色的數字是為了便於大家理解,計算出來的que[n].size。大家是否注意到圖中(1,2),(2,4),(2,3),(2,7),(7,8),(5,6)又分別存成了(2,1),(4,2),(3,2),(7,2),(8,7),(6,5)。這是因為根據lca離線演算法,上面提到的詢問(x,y)中,y是已處理過的結點。那麼,如果y尚未處理怎麼辦?所以,只要在查詢列表中加入兩個詢問(x, y)、(y,x),那麼就可以保證這兩個詢問有且僅有乙個被處理了(暫時無法處理的那個就pass掉)。

(3)lca演算法

假設為(u,v),u為此時已經搜完的子樹的根節點,v的位置就只有兩種可能,一種是在u的子樹內,另一種就是在其之外。

對於在u的子樹內的話,最近公共祖先肯定是可以直接得出為u;

對於在u的子樹之外的v,我們已經將v搜過了,且已經知道了v的祖先,那麼我們可以根據dfs的思想,v肯定是和u在一顆子樹下的,而且這顆子樹是使得他們能在一顆子樹下面深度最深的。而這個子樹的根節點剛好就是v的並查集所儲存的祖先。所以對於這種情況的(u,v),它們的最近公共祖先就是v的並查集祖先

。關於並查集,這篇部落格說的很清楚。

intfind(

intu)

//並查集,獲取nd的父親節點

intunion(

intu,

intv)

//並查集操作,將子樹節點和根節點合併在一起

else

return

1;  

}                通過pare這個陣列,可以找到當前節點的根節點。

最後:void

lca(

introot)  

vis[root]=1;  

sz=que[root].size();  

for(i=0;i

}  return

;  } 

根據tarjanlca的實現演算法可以看出,只有當某一棵子樹全部遍歷處理完成後,才將該子樹的根節點標記為已訪問(vis[root]=1),假設程式按上面的樹形結構進行遍歷,首先從節點1開始,然後遞迴處理根為2的子樹,然後遞迴處理根為5的子樹,當子樹處理完後,節點5標記為已訪問(vis[5]=1),處理和5相關的查詢(見程式lca中的14行,節點2未被訪問,跳過),返回,將(5,2)合併,pare[5]=2,修改子樹的祖先也指向root

。以此類推,當子樹2處理完畢後,節點2, 5, 6均已訪問;接著要回溯處理3子樹,首先被訪問的是節點7(因為節點7作為葉子不用深搜,直接處理),接著節點7就會檢視所有詢問(7, x)的節點對,假如存在(7, 5),因為節點5已經被訪問,所以就可以斷定(7, 5)的最近公共祖先就是find(5).ancestor,即節點1(因為2子樹處理完畢後,子樹2和節點1進行了union,find(5)返回了合併後的樹的根1,此時樹根的ancestor的值就是1)。   

建議:大家親自走一遍程式。

**:#include

#include

#include

using

namespace

std;  

#define size 11111  //節點個數

vector

> node[size],que[size];  

intn,pare[size],anse[size],in[size],rank[size],querynum;  

intvis[size];  

void

init()  

memset(vis,0,sizeof

(vis));  

memset(in,0,sizeof

(in));  

memset(anse,0,sizeof

(anse));  

}  int

find(

intu)

//並查集,獲取u的父親節點

intunion(

intu,

intv)

//並查集操作,將子樹節點和根節點合併在一起

else

return

1;  

} void

lca(

introot)  

vis[root]=1;  

sz=que[root].size();  

for(i=0;i

}  return

;  }  

intmain()  

}  intfind(

intu)

//並查集,獲取nd的父親節點

intunion(

intu,

intv)

//並查集操作,將子樹節點和根節點合併在一起

else

return

1;  

} while(querynum!=0)

for(i=1;i<=n;i++)  

if(in[i]==0) 

break

;//尋找根節點

lca(i);  

}  return

0;  

}  

LCA離線演算法tarjan

lca演算法 lca least common ancestor 是指在一棵樹中,距離兩個點最近的兩者的公共節點。也就是說,在兩個點通往根的道路上,肯定會有公共的節點,我們就是要求找到公共的節點中,深度盡量深的點。還可以表示成另一種說法,就是如果把樹看成是乙個圖,這找到這兩個點中的最短距離。本文先介...

LCA 離線tarjan演算法

對於最近公共祖先問題,我們先來看這樣乙個性質,當兩個節點 u,v 的最近公共祖先是x時,那麼我們可以確定的說,當進行後序遍歷的時候,必然先訪問完x的所有子樹,然後才會返回到x所在的節點。這個性質就是我們使用tarjan演算法解決最近公共祖先問題的核心思想。同時我們會想這個怎麼能夠保證是最近的公共祖先...

LCA的離線演算法Tarjan

總的來說是深度遍歷。以根為節點建立並查集。比如說求p,v的lca。當訪問到p的時候檢查v是否已經訪問過。如果訪問過,那麼他們的lca就是v現在所在的並查集裡面的元素。如果沒有訪問到。那麼等到v訪問到的時候可以操作一下。充分利用了遞迴這個操作,所以不必要儲存以前的東西,自然而然的就將u裡面的所有的子節...