1. 程式人生 > >樹上最近公共祖先(LCA)的算法

樹上最近公共祖先(LCA)的算法

序列 hide 搜索 進入 lap 兩個 for 倍增 pen

  有錯請大力指出【鞠躬】第一次寫正經博客非常慌張

LCA(Least Common Ancestors),即最近公共祖先,是指在有根樹中,找出某兩個結點u和v最近的公共祖先。
對於有根樹T的兩個結點u、v,最近公共祖先LCA(T,u,v)表示一個結點x,滿足x是u、v的祖先且x的深度盡可能大。
另一種理解方式是把T理解為一個無向無環圖,而LCA(T,u,v)即u到v的最短路上深度最小的點。
——百度百科

LCA的四種算法:

  • 記錄dfs序轉化為rmq問題(st表)
  • tarjan算法
  • 倍增算法
  • 樹鏈剖分

一、記錄dfs序轉化為rmq問題

  1.dfs序是什麽?

  其實本人對dfs序的定義也不怎麽清晰……望告知orz

  首先我們需要一顆樹……比如說它長這樣:

  技術分享圖片

  定義rt(root)為樹根,則在這棵樹中rt = 1。別說了我知道圖上是A

  本題需要用到的dfs序需在進入每個節點即搜完某一個兒子的時候記錄這個節點。從rt開始進行dfs(深度優先搜索),則得到的dfs序列為A B D B E B F B A C G H G C A。

  且讓我們跟著跑一遍。

    1.dfs(A) , 序列為A

    2.找到A的兒子B,dfs(B),序列為A B

    3.找到B的兒子D,dfs(D),序列為A B D

    4.發現D沒有兒子,返回B,序列為A B D B

    5.6.7.8. dfs(E),返回B,dfs(F),返回B,序列為A B D B E B F B

    9.搜完B的兒子,返回A,序列為A B D B E B F B A

    10.11.12. dfs(C),dfs(G),dfs(H),序列為A B D B E B F B A C G H

    13.14.15 返回G,返回C,返回A,序列為A B D B E B F B A C G H G C A

  結束。

  定義x為當前節點標號,dep為當前深度,h[i]為樹高,in[i]為每個節點被訪問到的序號,d[i]為dfs序。

  代碼如下:

技術分享圖片
void dfs(int x , int dep)
{
    d[++ cnt] = x ;
    h[x] = dep , in
[x] = cnt ; for(int i = fst[x] ; i ; i = e[i].nx) { if(e[i].x == fa[x]) continue ; fa[e[i].x] = x ; dfs(e[i].x , dep + 1) ; d[++ cnt] = x ; } }
View Code

  除了搜完根節點後結束dfs外,每個點被搜到和退出時均會在dfs序中加入一個數,因此該dfs序的長度為2 * n - 1。

  設點u、點v的最近公共祖先為點x,並且u在v之前被dfs到。因為x為u、v的祖先,所以dfs時先找到x,再從x往下找到u,即dfs(u)時已經過了x。記錄u後,返回到x再向下找到v。故在dfs序中,in[u]和in[v]之間必出現過x。而dfs(v)後返回時,x的子樹還沒有被搜完,所以不會出現x的祖先。這樣即可保證在in[u]、in[v]中高度最小的點即為u、v的最近公共祖先。

  這樣我們有了一個正確的算法,但如果對於每個詢問區間,都一位一位尋找最值,效率為O(mn),顯然不能接受。

樹上最近公共祖先(LCA)的算法