最近公共祖先(LCA)主要算法:

本文介绍了如何使用向上标记法、树上倍增法以及Tarjan算法来求解离线LCA问题。重点讲解了两种方法的原理、步骤以及并查集的优化策略,展示了在深度优先遍历中利用这些算法的有效性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1)向上标记法:

从x向上走到根节点,并标记所有经过的节点。

从y向上走到根节点,当第一次遇到已标记的节点时,就找到了LCA(x,y).

2)树上倍增法:

[1]先将两个点跳到同一层

[2]让两个点同时往上跳,一直跳到它们的最近公共祖先的下一层。

树上倍增法是一个很重要的算法。除了求LCA之外,他在很多问题中都有广泛应用。设F[x,k]表示x的2^k倍祖先,即从x走向根节点走2^k步到达的节点。特别地,若该节点不存在,则令F[x,y]=0。F[x,0]就是x的父节点。除此之外,任意k属于[1,logn],F[x,k]=F[F[x,k-1],k-1]。

这类似于一个动态规划的过程,“阶段”就是节点的深度。因此,我们可以对树进行广度优先遍历,按照层次顺序,在节点入队之前,计算它在F数组中相应的值。

以上部分处理是预处理,时间复杂度是O(nlogn),之后可以多次对不同的x,y计算LCA,每次询问的时间复杂度为O(logn)

基于F数组的计算LCA(x,y)分为以下几步。

1.设d[x]表示x的深度。不妨设d[x]>=d[y](否则可交换x,y)

2.用二进制拆分思想,把x向上调整到于y同一深度。具体来说,就是依次尝试从x向上走k=2^(logn),……,2^1,2^0步,检查到达节点是否比y深。每次检查中,若是,则令x=F[x,y]。

3.若此时x==y,说明已经找到了LCA,LCA就等于y。

4.用二进制思想拆分,把x,y同时向上调整,并保持深度一致且二者不会相同。具体来说,就是一次尝试把x,y同时向上走k=2^(logn),……,2^1,2^0步,若F[x,k]!=F[y,k](即仍未相会),则令x=F[x,k],y=F[y,k]。

5.此时x,y必定只差一步就相会了,它们的父节点F[x,0]就是LCA。

3)LCA的Tarjan算法

Tarjan算法本质上是使用并查集对“向上标记法”的优化。它是一个离线算法,需要把m个询问一次性读入,统一计算,最后统一输出。时间复杂度O(n+m)。

在深度遍历的任意时刻,树中的节点分为三类。

1.已经访问完毕并且回溯的节点。在这些节点上标记一个整数2.

2.已经开始递归,但尚未回溯的节点。这些节点就是当前正在访问的节点x以及x的祖先。在这些节点上标记一个整数1.

3.尚未访问的节点。这些节点没有标记。

对于正在访问的节点x,它到根节点的路径已经标记为1。若y是已经访问完毕并且回溯的节点,则LCA(x,y)就是从y向上走到根,第一个遇到的标记为1的节点。

可以利用并查集进行优化,当一个节点获得整数2的标记时,把它所在的集合合并到它的父节点所在的集合中(合并时它的父节点标记一定为1,且单独构成一个集合)。

这相当于每个完成回溯的节点都有一个指针指向它的父节点,只需查询y所在集合代表元素(并查集的get操作),就等价于从y向上一直走到一个开始递归但尚未回溯的节点(具有标记1),即LCA(x,y)。

在回溯点x之前,扫描所有与x相关的所有询问,若询问中的另一个点y的标记为2,就知道了该询问的回答应该是y在并查集中的代表元素(并查集中find(y)函数的结果)

1171. 距离(离线求LCA:tarjan算法)-优快云博客

————————————————————————————————————————

参考至算法进阶竞赛指南。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值