http://blog.csdn.net/taotaotaotao910429/article/details/7746650
发现网上对此算法真是多之又多,看了几个小时才算看懂。
写下我的理解思路,首先,LCA要用到并查集和深度优先搜索,其中并查集用来查找和合并各个节点集合,深度优先搜索用了搜索问题节点是否在同一个集合中。其实就是递归。(1):其中递的过程:首先算法从根开始,对每一棵子树进行深度优先搜索,访问根时,将创建由根结点构建的集合,然后把根节点的祖先设为自身,然后遍历该节点的每个子节点,也就是该节点的其他子树,如果子树是多层就选子节点重复上述过程,直到叶子节点。(2)归的过程:从叶子节点开始,找到其父节点,然后和父节点的集合合并,并把其祖先设为父节点,直到归到根节点。注意,在这过程中要判断问题节点是否在同一集合中,比如节点u,节点v,如果v在集合u中,那么他们最近公共祖先就应该是u,如果v不在u中,则遍历v时进行判断,自然就是v的最近祖先是v和u的最近公共祖先。
1.这个算法基于并查集和深度优先搜索。算法从根开始,对每一棵子树进行深度优先搜索,访问根时,将创建由根结点构建的集合,然后对以他的孩子结点为根的子树进行搜索,使对于 u, v 属于其某一棵子树的 LCA 询问完成。这时将其所有子树结点与根结点合并为一个集合。 对于属于这个集合的结点 u, v 其 LCA 必定是根结点。
2对于最近公共祖先问题,我们先来看这样一个性质,当两个节点(u,v)的最近公共祖先是x时,那么我们可以确定的说,当进行后序遍历的时候,必然先访问完x的所有子树,然后才会返回到x所在的节点。这个性质就是我们使用Tarjan算法解决最近公共祖先问题的核心思想。
同时我们会想这个怎么能够保证是最近的公共祖先呢?我们这样看,因为我们是逐渐向上回溯的,所以我们每次访问完某个节点x的一棵子树,我们就将该子树所有节点放进该节点x所在的集合,并且我们设置这个集合所有元素的祖先是该节点x。那么到我们完成对一个节点的所有子树的访问时,我们将这个节点标记为已经找到了祖先的点。
这个时候就体现了Tarjan采用离线的方式解决最近公共祖先的问题特点所在了,所以这个时候就体现了这一点。假设我们刚刚已经完成访问的节点是a,那么我们看与其一同被询问的另外一个点b是否已经被访问过了,若已经被访问过了,那么这个时候最近公共祖先必然是b所在集合对应的祖先c,因为我们对a的访问就是从最近公共祖先c转过来的,并且在从c的子树b转向a的时候,我们已经将b的祖先置为了c,同时这个c也是a的祖先,那么c必然是a、b的最近公共祖先。
对于一棵子树所有节点,祖先都是该子树的根节点,所以我们在回溯的时候,时常要更新整个子树的祖先,为了方便处理,我们使用并查集维护一个集合的祖先。总的时间复杂度是O(n+q)的,因为dfs是O(n)的,然后对于询问的处理大概就是O(q)的。
从网上找了这样一个容易理解算法的代码:http://blog.csdn.net/lixiandejian/article/details/6661074