最近公共祖先(LCA):离线&在线算法

问题:求两个结点的最近公共祖先(即在树中的公共祖先中高度最低的祖先),下面介绍两种适用于不同场景的算法。

Hiho15:离线Tarjan算法

基本思想

Tarjan算法适用于离线批量处理多个查询请求。基本思想是以深度优先搜索的顺序访问这颗树,给这棵树的结点染色,一开始所有结点都是白色的,而当第一次经过某个结点的时候,将它染成灰色,而当第二次经过这个结点的时候——也就是离开这棵子树的时候,将它染成黑色。

这样做的意义,举例说明,当我们深度优先搜索到A结点时,我们发现A结点和B结点是我们需要处理的一组询问。此时树结点的染色情况如下图:

发现B结点的颜色为灰色,那么最近公共祖先必然就是B结点(灰色代表第一次进入该结点的子树,A结点在B的子树里);

对于此时A和P的一组询问,发现P结点颜色为白色,则留待访问到P结点时处理;

还有A和C的一组询问,发现C为黑色,则LCA为C结点向上的第一个灰色结点。

  • 总结:我先计算每个结点涉及到的询问,然后在深度优先搜索的过程中对结点染色,如果发现当前访问的结点是涉及到某个询问,那么我就看这个询问中另一个结点的颜色,如果是白色,则留待之后处理,如果是灰色,那么最近公共祖先必然就是这个灰色结点,如果是黑色,那么最近公共祖先就是这个黑色结点向上的第一个灰色结点。

利用并查集查找黑色结点的最近灰色结点

还有一个问题就是如何快速找到黑色结点向上的第一个灰色结点:使用并查集维护。

想一下,当我们到达一个结点C时,该节点为灰色,进入该结点的子树回到该节点时,子树结点已经全部变黑,而这些黑色结点向上的第一个灰色结点就是当前结点C.

而此节点离开时变黑,他自己向上的第一个灰色结点就是它的父节点D,那么这一过程,其实就是将C结点代表的集合合并到了D结点代表的集合中去了,如下图:

  • 总结:每个结点最开始都是一个独立的集合,每当一个结点由灰转黑的时候,就将它所在的集合合并到其父亲结点所在的集合中去。这样无论什么时候,任意一个黑色结点所在集合的代表元素就是这个结点向上的第一个灰色结点!
  • 说明:用req数组表示每个结点的代表元素,对于灰色结点o的特点就是req[o]==o

所以查找的过程可以写成如下:

int Tree::find(int no) {
   if (no == req[no])
      return no;
   else{
      req[no] = find(req[no]);
      return req[no];
   }
}

实现该算法来解决hiho15题目时,为了快速找到树中某一结点做了映射,由string映射到树结点在数组中的索引值,这样就可以方便使用并查集。

Hiho17:在线算法

基本思想

就是每次询问都要直接给出结果,这里有一种在线算法,基本思想是从树的根结点深度优先搜索,每次经过某一个点——无论是从它的父亲节点进入这个点,还是每次从它的儿子节点返回这个点,都按顺序记录下来。这样就把一棵树转换成了一个数组,而找到树上两个节点的最近公共祖先,无非就是找到这两个节点最后一次出现在数组中的位置所囊括的一段区间中深度最小的那个点,转成数组后使用范围最小值算法(RMQ)即可搞定。

将树转为数组举例(图来自百度图片):

数组:5 2 8 6 2 7 2 1 3 1 4 1

注:这里我稍作调整,即非叶子结点的等每次访问完一个子节点后再记录,而叶子节点记录一次即可。

代码附录在我的github账号的leetcode仓库,如有需要请前往查看。

时间: 2024-12-13 21:07:04

最近公共祖先(LCA):离线&在线算法的相关文章

LCA(最近公共祖先)——离线 Tarjan 算法

一.梳理概念 定义:对于有根树T的两个结点u.v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u.v的祖先且x的深度尽可能大. 通俗地讲,最近公共祖先节点,就是两个节点在这棵树上深度最大的公共的祖先节点,即两个点在这棵树上距离最近的公共祖先节点. 提示:父亲节点也是祖先节点,节点本身也是它的祖先节点. 给出一棵树,如图所示: 由上面的定义可知:3和5的最近公共祖先为1,5和6的最近公共祖先为2,2和7的最近公共祖先为2, 6和7的最近公共祖先为4. 二.繁文缛节 注意注意注意!!!尚

LCA最近公共祖先 ST+RMQ在线算法

对于这一类的问题有2中解决方法.第一种就是tarjan的离线算法,还有一中是基于ST算法的在线算法.复杂度都是O(n); 先介绍在线算法: 1) dfs: 对于图所示的树,我们从根节点1开始dfs,按照先序访问(不算完全的先序),那么它访问顺序就是1 -> 2 -> 4 -> 2 -> 5 -> 7 -> 5 -> 8 -> 5 -> 2 -> 1 -> 3 -> 1 然后用数组first存第一次访问到该点时的时间(也就是访问顺序里面

求LCA最近公共祖先的离线Tarjan算法_C++

最近一直在刷算法,过几天再来写详细的思路 先丢个模板,这个是用双链树存的 1 #include<algorithm> 2 #include<iostream> 3 #include<cstdlib> 4 #include<cstring> 5 #include<cstdio> 6 #include<cmath> 7 #include<stack> 8 #define N 100001 9 using namespace s

我对最近公共祖先LCA(Tarjan算法)的理解

LCA 最近公共祖先 Tarjan(离线)算法的基本思路及我个人理解 首先是最近公共祖先的概念(什么是最近公共祖先?): 在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先,就是两个节点在这棵树上深度最大的公共的祖先节点. 换句话说,就是两个点在这棵树上距离最近的公共祖先节点. 所以LCA主要是用来处理当两个点仅有唯一一条确定的最短路径时的路径. 有人可能会问:那他本身或者其父亲节点是否可以作为祖先节点呢? 答案是肯定的,很简单,按照人的亲戚观念来说,你的父亲也是你的祖先,而

最近公共祖先LCA(Tarjan算法)的思考和算法实现——转载自Vendetta Blogs

最近公共祖先LCA(Tarjan算法)的思考和算法实现 LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现 小广告:METO CODE 安溪一中信息学在线评测系统(OJ) //由于这是第一篇博客..有点瑕疵...比如我把false写成了flase...看的时候注意一下! //还有...这篇字比较多 比较杂....毕竟是第一次嘛 将就将就 后面会重新改!!! 首先是最近公共祖先的概念(什么是最近公共祖先?): 在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先

最近公共祖先(LCA)问题

描述 对于有根树T的两个节点u和v,最近公共祖先LCA(T,u,v)表示一个节点x满足x是u,v的公共祖先且x的深度尽可能大. 算法 求解LCA问题主要有三种解法,分别是暴力搜索,Tanjar算法,最后一种是转化为RMQ问题,用DFS+ST算法来求解 暴力搜索 如果数据量不大的时候可以采用暴力搜索法.先将节点u的祖先节点全部标记出来,然后顺着节点v沿着父亲节点的方向向上遍历,直到遍历到一个被标记的节点,这个节点即为所求节点.或者分别获取u,v到根节点的路径P1,P2,可以将这两条路径看做两个两个

【C++】最近公共祖先LCA(前置知识)

1.前言 最近公共祖先(Least Common Ancestors),简称LCA,是由Tarjan教授(对,又是他)提出的一种在有根树中,找出某两个结点u和v最近的公共祖先问题. 2.什么是最近公共祖先? 在一棵树中,每个结点都有他的父亲和祖先,而最近公共祖先就是两个节点在这棵树上深度最大的公共的祖先节点. 换句话说,就是两个点在这棵树上距离最近的公共祖先节点.结合下图和文字应该很好的诠释了最近公共祖先: PS:在LCA中,也可以将结点本身视为自己的祖先 在这颗以结点1为根的树中,4与5的最近

hdu 3078(LCA的在线算法)

Network Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 847    Accepted Submission(s): 347 Problem Description The ALPC company is now working on his own network system, which is connecting all

LCA(倍增在线算法) codevs 2370 小机房的树

codevs 2370 小机房的树 时间限制: 1 s 空间限制: 256000 KB 题目等级 : 钻石 Diamond 题目描述 Description 小机房有棵焕狗种的树,树上有N个节点,节点标号为0到N-1,有两只虫子名叫飘狗和大吉狗,分居在两个不同的节点上.有一天,他们想爬到一个节点上去搞基,但是作为两只虫子,他们不想花费太多精力.已知从某个节点爬到其父亲节点要花费 c 的能量(从父亲节点爬到此节点也相同),他们想找出一条花费精力最短的路,以使得搞基的时候精力旺盛,他们找到你要你设计

POJ1330 Nearest Common Ancestors【最近公共祖先】【Tarjan-LCA算法】

Nearest Common Ancestors Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 19636Accepted: 10412 Description A rooted tree is a well-known data structure in computer science and engineering. An example is shown below: In the figure, each node