详解使用 Tarjan 求 LCA 问题(图解)

LCA问题有多种求法,例如倍增,Tarjan。

本篇博文讲解如何使用Tarjan求LCA。

如果你还不知道什么是LCA,没关系,本文会详细解释。

在本文中,因为我懒为方便理解,使用二叉树进行示范。

LCA是什么,能吃吗?

LCA是树上最近公共祖先问题。

最近公共祖先就是树上有两个结点,找一个结点,是他们的公共祖先,并且离他们两个结点最近。

例如这是一棵树:

树上 4,7 两个结点的 LCA 就是 2 了。

1 虽然也是他们的公共祖先,但并不是最近的。

再举个例子,8,5 的祖先是 5。8,6 的祖先是 1。

怎么求LCA问题?

在开头已经说过了,LCA 问题有多种求法。本文要介绍的是相对简单的 Tarjan 求 LCA。

注意:Tarjan 求 LCA 是一种离线的算法,也就是说它一遍求出所有需要求的点的 LCA,而不是需要求哪两个点再去求。

在开始介绍前的补充

Tarjan 求 LCA 需要用到并查集,以下是本人使用的并查集模板。

由于 Tarjan 是在遍历到目标点的时候得出答案并输出,那么如果你不输出,就需要使用一些东西来记录它(一般不用)。

关于记录

除非你之后需要 LCA 的结果再做一些操作,否则不需要记录,直接在 DFS 中输出即可。

我使用的是 STL 中的 Map 和 Pair,因为 LCA 是求两个点,Pair 正好可以满足一对数据。而 Map 的哈希机制可以实现 O(1) 查找。

Tarjan 求 LCA 做法

总体思想

遍历每一个结点并使用并查集记录父子关系。

Tarjan 是一种 DFS 的思想。我们需要从根结点去遍历这棵树。

当遍历到某一个结点(称之为 x) 时,你有以下几点需要做的。

1将当前结点标记为已经访问。

2递归遍历所有它的子节点(称之为 y),并在递归执行完后用并查集合并 x 和 y。

3遍历与当前节点有查询关系的结点(称之为 z)(即是需要查询 LCA 的另一些结点),如果 z 已经访问,那么 x 与 z 的 LCA 就是 $getfa(z)$(这个是并查集中的查找函数),输出或者记录下来就可以了。

这是伪代码

核心代码就这么一点?对,就这么一点。

如果你还不理解,那么可以跳转到最后一章看图解演示。

一些重要的细节

为了接下来的讲解,下面我们明确一下读入方式,不同的读入方式可以自己变通一下。

第一行两个数 n 和 q,表示结点数和查询数。

接下来 n 行每行两个数,表示左子结点和右子结点编号,如没有则是 -1。

接下来 q 行每行两个数,表示查询的两个结点编号。

例如上图的树,读入为

如何存储查询关系

我在这里用的方法是二维数组。

注意:需要双向存储关系。例如结点 2 和 3,不仅要更新t[2],还要更新t[3]。

读入代码长这样:

当然如果你想要优化下空间那么把这个数组变成vector也是没问题的。

这就没了…

代码

先记录,程序结束后输出的写法

算法演示


评论

  1. eeer

    好像图炸了?

    9月前
    2019-2-25 21:32:07
    • abc2237512422 博主

      修了,之前从博客园搬过来的,现在图传到这个博客里了

      9月前
      2019-2-25 22:05:59

发送评论


				
上一篇
下一篇