浅谈LCA的几种算法

LCA,Lowest Common Ancestor,最近的公共祖先。在一棵树中对于两个节点u , v找出节点T,使得T同时为uv的祖先。显然这样的T点肯定存在且有可能有多个,其中深度最大的那个点肯定为即为uv两点的LCA。关于LCA的解法有很多种,暴力枚举,事先需要知道所有询问的离线的tarjan算法和基于RMQ的在线算法,下面说一下自己对这种几种算法的理解。

 

⒈最容易想到的暴力搜索。

  给出节点u , v,,首先对u进行回溯一直到根节点,并对途中的节点加上标记。然后对v进行回溯,直到找到一个被标记的节点T,此时T即为uvLCA。此方法写起来很简单但时间复杂度太高,故只适合查询次数极少的时候。

 

⒉离线的Tarjan算法。

此种算法需要预先知道所有的询问,并对询问进行一些预处理,即将有相同节点的询问放在一块。算法的主要思想就是在DFS过程中处理一些信息从而得到答案。下面说一下我自己的理解。

DFS进行之前,把每个点都看作一个独立的点集且作为对应点集的代表元。在DFS过程中每次遍历完一棵子树,回溯到当前子树的根节点时,便将这棵子树上的所有点并到一个点集里,根节点即为该集合的代表元。然后对与此根节点相关的询问进行回答。设此节点为u,另一点为v,若v已经完成DFS,则v所在点集的代表元即为uvLCA

下面说一下细节:1.点集的标记可以用并查集来完成。2.每次询问都查询了两次,并且有且只有一次做出回答。3.此算法的缺点在于必须知道预先知道所有的询问,不够灵活。

 

⒊基于RMQ的在线查询算法。

该算法借助于DFS时的访问次序和RMQ的快速查询。

depth[]记录每个点的深度,r[]记录每个点在DFS过程中第一次被访问的次序。

刘汝佳的黑书上对此算法作了详细的介绍,当时读的时候有一句“由于每条边被访问了两次,因此一共记录了2n-1个节点”始终想不明白,后来发现在DFS的递归和回溯过程中,对于一个出度为X的点,肯定会被访问X+1次,1次在递归过程中,X在回溯过程中。又因为在一棵树中除根节点外,每个节点的入度均为1,故入度总和为n-1,又有入度 == 出度,所有所有的节点一共被访问了n+n-1次,n次在递归过程中,n-1次在回溯过程中。

r[u] < r[v] , point[2n-1]里面存放了DFS过程中一次被访问的节点。则在point[ r[u] ]到 point[ r[v] ](包括两端点)之间深度最小的那个点即为两点的LCA,深度最小的点有且仅有一个。此时可分为两种情况,一种是u即为uvLCA,第二种就是第三点wuvLCA

第一种情况中显然v在一棵以u为根节点的子树上,此时显然在DFS过程中先u先放入point[],然后在DFS遍历这个子树的过程中v放入point[],所以在[ r[u] , r[v] ] 中 u即为深度最小的那个点。

第二种情况中DFS过程中必先会遍历完其中一个点所在的子树,然后会回溯到某一节点w继续DFS遍历该节点的其他子树,如果v在此时遍历的子树上,则w即为uvLCA。由上可知,point[][ r[u] , r[v] ] 区间内必有w

区间内的快速查询可以用到RMQ或者线段树,这里就不再赘述。

以上就是我对LCA的几种算法的理解,感觉说的有点罗嗦,不足之处还望指出。

 

阅读更多
个人分类: 数据结构
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭