在一棵有根树中,两个结点u和v的最近公共祖先是指这样的一个结点 w,是u和v的祖先,并且在树T中具有最在深度。
tarjan离线算法 ,伪代码如下:
LCA(u)
MAKE-SET(u)
ancestor[FIND-SET(u)] = u
for each child v of u in T
LCA(v)
UNION(u,v)
ancestor[FIND-SET(u)]=u
color[u] = BLACK
for each node v such that [u,v] 属于P
if color[v] = BLACK
print "The least common ancestor of " u "and" v "is" ancestor[FIND-SET(v)]
平方根分段法
将高度为h的树分成sqrt(h)段,用P(x)来表示结点x的祖先结点,将第一段中的所有结点P(x)设置为根结点(根结点标号为1),从第二段开始,每段的第一层,即上图中的结点5,6,7,12,13的P(x)设置为x的父结点,其它层结点上的P(x)设置为P(x的父结点)。对于查询LCA(x,y),先调整P(x)和P(y),使得在同一段,然后根据其结点所在的深度,向上转直到x,y 的父结点相等。在使用DFS遍历得到深度及父结点关系,P(x)关系时,时间复杂度为O(n),在查询时时间复杂度为O(sqrt(n))。其伪代码如下
function dfs(u, father, depth)
parent[u] = father
height[u] = depth
for v on edge [u,v]
if v != father
dfs(v, u, depth + 1)
function calP(u, father, sections)
if height[u] < sections
P(u)= 1
else if height[u] % sections == 0)
P(u) = parent[u]
else
P(u) = P(parent[u])
for v on edge [u,v]
if v != father
calP(v, u, sections)
function LCA(u,v)
while (P(u) != P(v))
if (height(u) < height(v))
v = P(v)
else
u = P(u)
while (u != v)
if (height(u) < height(v))
v = parent(v)
else
u = parent(u)
树上倍增算法 :
与上面方法有些类似。用st[i][j]表示结点i的第2^j个祖先结点,有st[i][j] = st[st[i][j-1]][j-1]。在求LCA(u,v)时,将深度深的移动到深度浅的,即在height(u)>height(v)时,将u移动height(u)-height(v)次,移动相同深度位置,然后再同步上移。算法如下:
LCA(u,v)
if height(u) > height(v)
swap(u,v)
h = height(v)-height(u)
for i in 0 to maxlog
if h & (1 <<i)
v = st[v][i]
if u ==v
return
for i in maxlog to 0
if st[u][i] != st[v][i]
u = st[u][i]
v = st[v][i]
return parent[u]
RMQ算法
通过对根深度优先搜索得到欧拉序用E[0,2n-1]表示,L[i]表示结点E[i]的深度,用R[i]表示 E中第一次出现i的位置。LCA(u,v)=RMQ(L, R(u), R(v)),通过 sparse table来计算区间最值,时间复杂度为O(nlogn),查询时间复杂度为O(1).
参考资料: