最近公共祖先(LCA)的Tarjan算法

最近公共祖先(LCA)问题

LCA(T,u,v):在有根树T中,询问一个距离根最远的结点x,使得x同时为结点u、v的祖先

LCA问题可以用朴素的DFS方法解决,但是时间复杂度就很高了,这里介绍一种高级一点的解决LCA问题的Tarjan算法。

Tarjan算法是由Robert Tarjan在1979年发现的一种高效的离线算法,也就是说,它要首先读入所有的询问(求一次LCA叫做一次询问),然后并不一定按照原来的顺序处理这些询问。

首先需要有一些预备知识:

1.基本图论

这个就不多讲了,如果有不知道的可以随便抓一本数据结构的书恶补一下。

2.并查集

并查集其实也是很简单的东西,实现的代码都不超过10行。

这里提一下并查集的概念,并查集是一种处理元素之间等价关系的数据结构,一开始我们假设元素都是分别属于一个独立的集合里的,主要支持两种操作:

  1. 合并两个不相交集合(Union)
  2. 判断两个元素是否属于同一集合(Find)

需要知道一点,就是并查集的Find操作的时间复杂度是常数级别的。

tarjan_figure1.jpg

考察树 T 中所有与结点 u 有关的询问 (u, v)
对于子树 u 中的结点 v ,满足 LCA(u, v) = u
对于子树 p1 而非子树 u 中的结点 v ,满足 LCA(u, v) = p1
对于子树 p2 而非子树 p1 中的结点 v ,满足 LCA(u, v) = p2
tarjan_figure2.jpg
算法DFS有根树T,定义从根节点到当前正在遍历的结点u的路径为活跃路径P
对于每个已经遍历过的结点x,我们用并查集将其连接到P上距离其最近的结点Find(x)
记录与u有关的询问集合为Q(u)
tarjan_figure3.jpg
对于Q(u)中的任意一组询问LCA(u, v),如果v已经遍历过,那么答案就是Find(v),所以我们只需要维护当前所有以遍历结点的F即可。
tarjan_figure4.jpg
第一次遍历结点u时,有Find(u) = u;
遍历完子树u后,子树u内任意结点v均有F(v) = u;回溯回结点p1时,子树u内任意结点v均有F(w) = p1,使用并查集完成即可。
算法流程:
Tarjan(u)
F(u)<-u;
For each (u,v) in Q(u) do Answer(u,v) <- F(v)
For each v in son(u)
a) Tarjan(v);
b) F(v) <- u;
这里的F用并查集来实现。
这样LCA的Tarjan算法就可以完整的实现了,这个算法的时间复杂度取决于并查集Find操作的时间复杂度,如果采用不相交集森林的方法来实现并查集并采用路径压缩来优化,这样Find操作的时间复杂度可以认为是常数级别的。
所以Tarjan算法的时间复杂度就是O(Na(N) + Q),a(N)在可以计算的范围内是一个小于4的常数,空间复杂度为O(N),其中N表示问题规模,Q表示询问次数。

转载于:https://www.cnblogs.com/ACAC/archive/2010/05/26/1744766.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
最近公共祖先(Lowest Common Ancestor, LCA)问题是指在给定一棵树中找到两个节点的最短路径上的共同祖先。Tarjan算法通常用于解决这个问题,但它的主要目的是为了发现图中的强连通分量(Strongly Connected Components, SCC),而不是直接计算LCA。不过,由于这两种问题都涉及到深度优先搜索和拓扑排序的思想,所以我们可以借助Tarjan算法的思路来理解LCATarjan算法是基于深度优先搜索和一种称为“DFS树”的数据结构。在寻找LCA的过程中,如果能找到两个节点在同一棵DFS树或它们的DFS祖先相同,那么这两个节点就是最近公共祖先。这里的关键在于维护节点的前驱(pred)和后继(succ)指针,以及一个秩(rank)数组来判断边的方向,以确定节点是否构成一个回路。 下面是 Tarjan 算法的主要步骤: 1. 初始化:对于每个未访问的节点 u,设置其秩 rank[u] = 次序号(u 的编号),低link[u] = u(表示 u 的父节点),深度 depth[u] = 0,访问次数和栈顶指针为 null。 2. DFS 递归过程:从根节点开始遍历,对每个子节点 v,执行以下操作: a. 如果 v 没有被访问过,则进行一次深度优先搜索,更新深度、秩和低link信息。 b. 记录 v 的秩 rank[v] 和当前的深度 depth[v],并将 v 加入到相应的 DFS 树。 c. 更新 v 的所有前驱和后继指针。 d. 如果 v 是回路的一部分,将 v 设置为它自身的低link,这会导致算法在下一个阶段检测到回路。 3. 完成搜索后,对每个节点 u,检查 lowlink[u] 是否等于 u,如果是,说明 u 和 u 是同一个强连通分量内的节点。同时,这也帮助我们找到 u 和其他节点的最近公共祖先,因为他们在同一棵树上。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值