LCA(lowest common ancestor)问题

转自大神http://blog.163.com/zhaohai_1988/blog/static/209510085201263195947966/的博客

问题描述

 LCA:Least Common Ancestors(最近公共祖先),对于一棵有根树T(不一定是二叉树哦)的任意两个节点u,v,求出LCA(T, u, v),即离根节点root最远的节点x,使得x同时是u和v的祖先。
 

对于该问题,最容易想到的算法是分别从节点u和v回溯到根节点,获取u和v到根节点的路径P1,P2,其中P1和P2可以看成两条单链表,这就转换成常见的一道面试题:【判断两个单链表是否相交,如果相交,给出相交的第一个点。】。该算法总的复杂度是O(n)(其中n是树节点个数)。

本文介绍了两种比较高效的算法解决这个问题

      在线算法:用比较长的时间做预处理,但是等信息充足以后每次回答询问只需要用比较少的时间。
在线算法是DFS+ST。其中ST(sparse-table)算法在RMQ算法那篇文章中有详细解释。
                RMQ:给出一个数组A,回答询问RMQ(A, i, j),即A[i...j]之间的最值的下标。  
      离线算法:先把所有的询问读入,然后一起把所有询问回答完成。 (Tarjan算法 )
问题的解决
在线算法DFS+ST(思想是:将树看成一个无向图,u和v的公共祖先一定在u与v之间的最短路径上):

(1)DFS:从树T的根开始,进行深度优先遍历(将树T看成一个无向图),并记录下每次到达的顶点。第一个的结点是root(T),每经过一条边都记录它的端点(欧拉环游)。由于每条边恰好经过2次,因此一共记录了2n+1个结点,用E[1, ... , 2n+1]来表示。比如

 



 
我们可以建立三个数组:
E[1, 2*N-1]  - 对T进行欧拉环游过程中所有访问到的结点;E[i]是在环游过程中第i个访问的结点
L[1,2*N-1] -  欧拉环游中访问到的结点所处的层数;L[i]E[i]节点所在的层数R[1, N] ------ R[i] E中结点i第一次出现的下标(任何出现i的地方都行,当然选第一个不会错)

 

(2)计算R:用R[i]表示E数组中第一个值为i的元素下标,即如果R[u] < R[v]时,DFS访问的顺序是E[R[u], R[u]+1, …, R[v]]。虽然其中包含u的后代,但深度最小的还是u与v的公共祖先。

(3)RMQ:当R[u] ≥ R[v]时,LCA[T, u, v] = RMQ(L, R[v], R[u]);否则LCA[T, u, v] = RMQ(L, R[u], R[v]),计算RMQ。

由于RMQ中使用的ST算法是在线算法,所以这个算法也是在线算法。

(图中的H数组就是R数组)

 

 

 

 

 

再拿一个更小的例子讲解。
将有向树看成无向树,对于 u 和 v 的最近公共主祖先,则可以证明,最近公共祖先必定在 u 通往 v 的最短路径上,并且是最短路径上深度最小的结点。先对树进行 DFS, 保存其 DFS 序列, 再在序列找深度最小的结点。
 
例如:
对于树 <V,E>, V= { 1, 2, 3, 4, 5 }, E= { <1,2>, <1,3>, <3,4>, <3,5> }
对其进行 DFS 访问,记录的一种 DFS 访问路径为( 用E[]记录 ):
E[i]: 1 2 1 3 4 3 5 3 1       对应的结点在树中的深度为( 用L[]记录 ):
L[i]: 0 1 0 1 2 1 2 1 0
对于求 u 和 v 的最近公共祖先,先找到 u 和 v 在E[]中第一次出现的位置(其实可以用一个数组R[]记录下来,如前文介绍), 如 u= 2, v= 3 时, 2 在 E[] 中第一次出现的位置在 E[] 中下标为 1, 3第一次有 E[] 中出现的下标为 3, 考虑 E[] 中,下标从 1 到 3 这一段, { 2, 1, 3 } 对应深度为 { 1, 0, 1 } 深度最小为 0, 对应的结点为 1, 所以 u= 2, v= 3 的最近公共祖先为 1。
 
对一个数组中某一段求最值可以用 RMQ 来做(用sparse-table的算法),所以 LCA 问题就转化为了 RMQ 问题了。
posted @ 2014-07-14 11:28  狂徒归来  阅读(280)  评论(0编辑  收藏  举报