lca演算法樸素演算法
也就是我們所說的暴力演算法,大致的思路是從樹根開始,往下迭代,如果當前結點比兩個結點都小,那麼說明要從樹的右子樹中找;相反則從左子樹中查詢;直到找到乙個結點在當前結點的左邊,乙個在右邊,說明當前結點為最近公共祖先,如果乙個結點是另外乙個結點的祖先,那麼返回前面結點的父親結點即可。class node:
val = 0
left = right = none
def __init__(self, val=0):
self.val = val
def getlca(current, p, q):
left_val, right_val = p.val, q.val
parent = node()
# 保證左結點的值小於右結點
if left_val > right_val:
left_val, right_val = right_val, left_val
while true:
if current.val > right_val:
parent = current
current = current.left
elif current.val < left_val:
parent = current
current = current.right
elif current.val == left_val or current.val == right_val:
return parent.val
else:
return current.val
如果這不是一顆bst,那麼同樣的可以使用遞迴來計算,其原理是找到兩個相對應的結點,如果不存在則返回空,然後向上遞迴,如果當前結點的左子結點和右子結點同時存在,那麼說明這是最近公共祖先。def getlca2(root, node1, node2):
if root == none:
return none
if root == node1 or root == node2:
return root
left = getlca2(root.left, node1, node2)
right = getlca2(root.right, node1, node2)
if left != none and right != none:
return root
elif left != none:
return left
elif right != none:
return right
else:
return none
這種做法的缺點在於,每一次查詢都需要重新計算,顯然不適用於批量查詢。tarjan演算法
可以把樹看作是乙個有向圖,在有向圖g中,如果兩個頂點間至少存在一條路徑,稱兩個頂點強連通(strongly connected)。如果有向圖g的每兩個頂點都強連通,稱g是乙個強連通圖。非強連通圖有向圖的極大強連通子圖,稱為強連通分量(strongly connected components)。那麼這個問題就轉化成了如何在有向圖中查詢強連通,而tarjan演算法正好是解決這個的演算法。
比如在這個圖中,1,2,4,5,6,7,8構成乙個強連通分量,而由於3和9都無法達到強聯通分量的相互聯通的要求,因此各自單獨構成乙個強連通分量。
tarjan演算法的基本框架:遍歷乙個點,指定乙個唯一時間戳dfn[i],指定該點向前追溯可追溯到的最老時間戳low[i];
列舉當前點所有邊,若dfn[j]=0表明未被搜尋過,遞迴搜尋之;
若dfn[j]不為0,則j被搜尋過,這時判斷j是否在棧中,且j的時間戳dfn[j]小於當前時間戳dfn[i],可判定成環.將low[i]設定為dfn[j];
若這個點low[i]和dfn[i]相等,說明這個節點是所有強連通分量的元素中在棧中最早的節點,也就是我們要找的跟;
彈棧,將這個強連通分量全部彈出,縮成點。
tarjan演算法在dfs的過程中維護了一些資訊:dfn、low和乙個棧。dfn[i]:表示結點 i 在dfs中是第幾個被訪問到的結點,稱為時間戳;
low[i]:表示結點 i 出發,通過有向邊可以到達的所有結點中最小的index;
棧:儲存當前已經訪問過,但是沒有被歸類為任何的乙個強連通分量的結點;
演算法的關鍵在於如何判定某結點是否是強連通分量的根,在這裡根結點指深度優先搜尋時強連通分量中首個被訪問的結點。需要注意的是,棧中的結點不是在以它為根的子樹搜尋完成後出棧,而是在整個強連通分量被找到時。void tarjan(int i)//tarjan
int j;
dfn[i]=low[i]=++dindex;//index 時間戳
instack[i]=true;//標記入棧
stap[++stop]=i;//入棧
for (edge *e=v[i];e;e=e->next)//列舉所有相連邊
j=e->t;//臨時變數
if (!dfn[j])//j沒有被搜尋過
tarjan(j);//遞迴搜尋j
if (low[j]
low[i]=low[j];//更新能達到老時間戳
else if (instack[j] && dfn[j]
low[i]=dfn[j];//當前記錄可追溯時間戳更新
if (dfn[i]==low[i])//可追溯最小的index是自己,表明自己是當前強連通分量的跟
bcnt++;//強連通分量數增加
//在當前結點之**棧並且還不屬於其它的強連通分量的結點構成以當前結點為跟的強連通分量
doj=stap[stop--];//出棧頂元素
instack[j]=false;//標記出棧
belong[j]=bcnt;//記錄j所在的強連通分量編號
while (j!=i);//如果是當前元素,彈棧完成
void solve()
int i;
stop=bcnt=dindex=0;
memset(dfn,0,sizeof(dfn));//標記為為搜尋過
for (i=1;i<=n;i++) // 確保所有結點都被訪問到
if (!dfn[i])
tarjan(i);
同樣的,根據程式對之前的那種圖進行分析,得到最後的結果,其中每個結點上面的值代表的是該結點在有向邊中能夠到達的最小的index。
除了上面提到的演算法,還有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,表示詢問...
最近公共祖先
題目 二叉樹的結點定義如下 struct treenode 輸入二叉樹中的兩個結點,輸出這兩個結點在數中最低的共同父結點。分析 求數中兩個結點的最低共同結點是面試中經常出現的乙個問題。這個問題至少有兩個變種。第一變種是二叉樹是一種特殊的二叉樹 查詢二叉樹。也就是樹是排序過的,位於左子樹上的結點都比父...