最近公共祖先問題

2021-06-23 02:36:56 字數 3332 閱讀 5385

最近公共祖先(least common ancestors)問題是面試中經常出現的乙個問題,這種問題變種很多,解法也很多。最近公共祖先問題的定義如下:

對於有根樹t的兩個結點u、v,最近公共祖先lca(t,u,v)表示乙個結點x,滿足x是u、v的祖先且x的深度盡可能大。另一種理解方式是把t理解為乙個無向無環圖,而lca(t,u,v)即u到v的最短路上深度最小的點。

例如,對於下面的樹,結點4和結點6的最近公共祖先lca(t,4,6)為結點2。

面試中lca問題的擴充套件主要在於結點是否只包含父結點指標,對於同一棵樹是否進行多次lca查詢。下面分別進行說明。

首先可以計算出結點u和v的深度d1和d2(由於只有parent指標,沿著parent指標一直向上移動即可計算出它的深度)。如果d1>d2,將u結點向上移動d1-d2步,如果d1int

getdepth

(treenode

*node

)treenode

*getlca

(treenode

*node1

,treenode

*node2

)while

(d1

d2--,

node2

=node2

->

parent

;while

(node1

!=node2

)return

node1

;}該演算法時間複雜度為o(h),空間複雜度為o(1),其中h為樹的高度。

第一種演算法每一次查詢的時間複雜度都是o(h),如果需要對同一棵樹進行多次查詢,有沒有更快的演算法呢?觀察第一種演算法,主要進行的操作是將某個結點u沿著parent指標向上移動n步,我們可以對樹進行一些預處理加速這個過程,這裡使用到了動態規劃的思想。

設p[i][j]表示結點i往上移動2^j步所到達的結點,p[i][j]可以通過以下遞推公式計算:

利用p陣列可以快速的將結點i向上移動n步,方法是將n表示為2進製數。比如n=6,二進位制為110,那麼利用p陣列先向上移動4步(2^2),然後再繼續移動2步(2^1),即p[ p[i][2] ][1]。

預處理計算p陣列**如下:

map

<

treenode

*,int

>

nodetoid

;map

<

int,

treenode

*>

idtonode

;const

intmaxlogn=20

;//樹中最大結點數為1<<20

intp[1

<<

maxlogn

][maxlogn

];//allnodes存放樹中所有的結點

void

preprocesstree

(vector

<

treenode

*>

allnodes

)// p[i][0]=parent(i)

for(

inti =0

;i i++)p[

i][0]

=allnodes[i

]->

parent

?nodetoid

[allnodes[i

]->

parent]:

-1;// 計算p[i][j]

for(

intj =1

;1<<

j j++)

for(

inti =0

;i i++)if(

p[i][

j]!=-

1)p[

i][j]

=p[p

[i][j -1

]][j -1

];}另外我們還需要預處理計算出每個結點的深度l,預處理之後,查詢node1和node2的lca演算法如下。

treenode

*getlca

(treenode

*node1

,treenode

*node2

,intl)

時間複雜度分析:假設樹包含n個結點,由於p陣列有nlogn個值需要計算,因此預處理的時間複雜度為o(nlogn)。查詢兩個結點的lca時,函式getlca中兩個迴圈最多執行2logn次,因此查詢的時間複雜度為o(logn)。

這裡我們只考慮二叉樹,樹中結點包含左右兒子結點指標。給定樹根結點t,以及樹中u,v結點,需要計算lca(t,u,v)。可以採用遞迴的方法,對於結點node,如果在node左子樹或者右子樹中找到了lca(u,v),那麼直接返回這個答案。否則如果node子樹同時包含了u,v結點,那麼node結點即為lca(u,v)。否則在當前node子樹中找不到lca(u,v)。

struct

treenode

;//在子樹node中查詢lca(u,v),同時u,v在node子樹中的出現情況記錄到flag中

//如果沒找到lca(u,v),返回null

treenode

*getlcahelper

(treenode

*node

,treenode*u

,treenode*v

,int

&flag)if

(node

->

right

!=null)if

(u ==node

)flag |=1

;//標記u在子樹node中if(

v ==

node

)flag |=2

;//標記v在子樹node中

flag

|=leftflag

;flag

|=rightflag;if

(flag ==3

)return

node

;//u,v都出現在node子樹中

return

null;}

//計算lca(root, node1, node2)

treenode

*getlca

(treenode

*root

,treenode

*node1

,treenode

*node2

)時間複雜度分析:該遞迴演算法最多訪問每個樹結點一次,因此時間複雜度為o(n)。

這種情況同樣可以使用演算法2來提高每次查詢的效率,預處理過程中先遍歷樹,記錄每個結點的深度和父親結點指標,然後計算p陣列,查詢過程和演算法2一樣。這樣,預處理的時間複雜度為o(nlogn),查詢一次的時間複雜度為o(logn)。

最近公共祖先問題

在樹中尋找兩個結點的最近公共祖先 從樹的根節點開始進行深度優先搜尋,每次經過某乙個點 無論是從它的父親節點進入這個點,還是從它的兒子節點返回這個點,都按順序記錄下來。這樣就把一棵樹轉換成了乙個陣列。而找到樹上兩個節點的最近公共祖先,無非就是找到這兩個節點最後一次出現在陣列中的位置所囊括的一段區間中深...

最近公共祖先 python 最近公共祖先

lca演算法樸素演算法 也就是我們所說的暴力演算法,大致的思路是從樹根開始,往下迭代,如果當前結點比兩個結點都小,那麼說明要從樹的右子樹中找 相反則從左子樹中查詢 直到找到乙個結點在當前結點的左邊,乙個在右邊,說明當前結點為最近公共祖先,如果乙個結點是另外乙個結點的祖先,那麼返回前面結點的父親結點即...

最近公共祖先 LCA 最近公共祖先

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