夜深人静写算法(六) - 最近公共祖先
目录
一、引例
1、树-结点间最短距离
二、LCA(最近公共祖先)
1、朴素算法
2、步进法
3、记忆化步进法
4、tarjan算法
5、doubly算法
三、并查集
1、"并"和"查"
2、朴素算法
3、森林实现
4、启发式合并
5、路径压缩
6、元素删除
四、RMQ
1、朴素算法
2、线段树(简介)
3、ST算法
五、最近公共祖先相关题集整理
一、引例
1、树-结点间最短距离
【例题1】给定一棵n(n <= 100000)个结点的树,以及m(m <= 100000)条询问(u, v),对于每条询问,求u和v的最短距离?
我们知道,一个普通的无向图求两点间最短距离可以采用单源最短路,将时间复杂度大致控制在O(nlogn),但是当询问足够多的时候,这并不是一个高效的算法。从树的性质可知,树上任意两点间有且仅有一条简单路径(路径上没有重点和重边),所以树上任意两点间的最短距离其实就是这条简单路径的长度。如图一-1-1所示,要求u到v的距离,我们需要知道红色路径A(u->r),蓝色路径B(v->r),红蓝公共路径C(a->r),那么u->v的路径显然就可以通过A、B、C三者的长度计算出来,令dist[x]表示从x到树根r的简单路径的长度,则u到v的距离可以表示成如下:dist[u] + dist[v] - 2*dist[a]。
那么问题来了,a是什么,我们来看a->r路径上的所有结点既是u的祖先,也是v的祖先,所以我们给它们一个定义称为公共祖先(Common Ancestors),而a作为深度最深的祖先,或者说离u和v最近的,我们称它为最近公共祖先(Lowest Common Ancestors),简称LCA。
图一-1-1
二、LCA(最近公共祖先)
1、朴素算法
于是求树上两点间的距离转化成了求两个结点的最近公共祖先问题上来了,最容易想到的办法是将u->r和v->r的两条路径通过递归生成出来,并且逆序保存,然后比较两条路径的公共前缀路径,显然公共前缀路径的尾结点就是u和v的最近公共祖先,但是这个算法在树退化成链的时候达到最坏复杂度O(n),并不可行。
2、步进法
对于两个结点u和v,假设它们的最近公共祖先为lca(u, v),用depth[x]表示x这个结点的深度,pre[x]表示x的父结点。那么很显然,有depth[ lca(u, v) ] <= min{depth[u], depth[v]},通过这样一个性质,我们可以很容易得出一个算法:
1) 当depth[u] < depth[v]时,lca(u, v) = lca(u, pre[v]);