概述篇

LCA(Least Common Ancestors),即最近公共祖先,是指这样的一个问题:在一棵有根树中,找出某两个节点 uv 最近的公共祖先。

LCA可分为在线算法离线算法

  • 在线算法:指程序可以以序列化的方式一个一个处理输入,也就是说在一开始并不需要知道所有的输入。
  • 离线算法:指一开始就需要知道问题的所有输入数据,而在解决一个问题后立即输出结果。

算法篇

对于该问题,很容易想到的做法是从 u、v 分别回溯到根节点,然后这两条路径中的第一个交点即为 u、v 的最近公共祖先,在一棵平衡二叉树中,该算法的时间复杂度可以达到\(O(logn)\),但是对于某些退化为链状的树来说,算法的时间复杂度最坏为\(O(n)\),显然无法满足更高频率的查询。

本节将介绍几种比较高效的算法来解决这一问题,常见的算法有三种:在线DFS+ST算法、倍增算法、离线Tarjan算法。

接下来我们来一一解释这两种的算法。

在线 DFS + ST 算法

首先看到 ST 你会想到什么呢?(脑补许久都没有想到它会是哪个单词的缩写)LCA的在线算法是可以建立在RMQ问题的基础上的

我们设 LCA(T,u,v) 为在有根树 T 中节点 u、v 的最近公共祖先, RMQ(A,i,j) 为线性序列 A 中区间 [i,j] 上的最小(大)值。

如下图这棵有根树:

我们令节点编号满足父节点编号小于子节点编号(编号条件)

可以看出 LCA(T,4,5) = 2, LCA(T,2,8) = 1, LCA(T,3,9) = 3

设线性序列 A 为有根树 T 的中序遍历,即 A = [4,2,5,1,8,6,9,3,7]

由中序遍历的性质我们可以知道,任意两点 u、v 的最近公共祖先总在以该两点所在位置为端点的区间内,且编号最小。

举个栗子:

假设 u = 8, v = 7 ,则该两点所确定的一段区间为 [8,6,9,3,7] ,而区间最小值为 3 ,也就是说,节点 3u、v 的最近公共祖先。

解决区间最值问题我们可以采用RMQ问题中的 ST 算法

但是在有些问题中给出的节点并不一定满足我们所说的父节点编号小于子节点编号,因此我们可以利用节点间的关系建图,然后采用前序遍历来为每一个节点重新编号以生成线性序列 A ,于是问题又被转化为了区间最值的查询,和之前一样的做法咯~

时间复杂度:\(n \times O(logn)\)预处理+$ O(1)$查询

\(RMQ\)问题的解法


以上部分介绍了LCA如何转化为RMQ问题,而在实际中这两种方案之间可以相互转化

类比之前的做法,我们如何将一个线性序列转化为满足编号条件的有根树呢?

  1. 设序列中的最小值为\(A_{k}\),建立优先级为\(A_{k}\)的根节点\(T_{k}\)
  2. 将\(A[1...k−1]\)递归建树作为\(T_{k}\)的左子树
  3. 将\(A[k+1...n]\)递归建树作为\(T_{k}\)的右子树

读者可以试着利用此方法将之前的线性序列 A=[4,2,5,1,8,6,9,3,7] 构造出有根树 T ,结果一定满足之前所说的编号条件,但却不一定唯一。

离线 Tarjan 算法

Tarjan算法是一种常见的用于解决LCA问题的离线算法,它结合了深度优先搜索与并查集,整个算法为线性处理时间。

首先来介绍一下Tarjan算法的基本思路:

  1. 任选一个节点为根节点,从根节点开始
  2. 遍历该点u的所有子节点v,并标记v已经被访问过
  3. v还有子节点,返回2,否则下一步
  4. 合并vu所在集合
  5. 寻找与当前点u有询问关系的点e
  6. e已经被访问过,则可以确定ue的最近公共祖先为e被合并到的父亲节点

伪代码:

Tarjan(u)//merge和find为并查集合并函数和查找函数
{
for each(u,v)//遍历u的所有子节点v
{
Tarjan(v);//继续往下遍历
merge(u,v);//合并v到u这一集合
标记v已被访问过;
}
for each(u,e)//遍历所有与u有查询关系的e
if (e被访问过) u,e的最近公共祖先为find(e);
}

感觉讲到这里已经没有其它内容了,但是一定会有好多人没有理解怎么办呢?

我们假设在如下树中模拟Tarjan过程(节点数量少一点可以画更少的图)

存在查询: LCA(T,3,4),LCA(T,4,6),LCA(T,2,1)

注意:每个节点的颜色代表它当前属于哪一个集合,橙色线条为搜索路径,黑色线条为合并路径。

当前所在位置为 u = 1 ,未遍历孩子集合 v = {2,5} ,向下遍历。

当前所在位置为 u = 2 ,未遍历孩子集合 v = {3,4} ,向下遍历。

当前所在位置为 u = 3 ,未遍历孩子集合 v = {} ,递归到达最底层,遍历所有相关查询发现存在 LCA(T,3,4) ,但是节点 4 此时标记未访问,因此什么也不做,该层递归结束。

递归返回,当前所在位置 u = 2 ,合并节点 3u 所在集合,标记 vis[3] = true ,此时未遍历孩子集合 v = {4} ,向下遍历。

当前所在位置 u = 4 ,未遍历孩子集合 v = {} ,遍历所有相关查询发现存在 LCA(T,3,4) ,且 vis[3] = true ,此时得到该查询的解为节点 3 所在集合的首领,即 LCA(T,3,4) = 2 ;又发现存在相关查询 LCA(T,4,6) ,但是节点 6 此时标记未访问,因此什么也不做。该层递归结束。

递归返回,当前所在位置 u = 2 ,合并节点 4u 所在集合,标记 vis[4] = true ,未遍历孩子集合 v = {} ,遍历相关查询发现存在 LCA(T,2,1) ,但是节点 1 此时标记未访问,因此什么也不做,该层递归结束。

递归返回,当前所在位置 u = 1 ,合并节点 2u 所在集合,标记 vis[2] = true ,未遍历孩子集合 v = {5} ,继续向下遍历。

当前所在位置 u = 5 ,未遍历孩子集合 v = {6} ,继续向下遍历。

当前所在位置 u = 6 ,未遍历孩子集合 v = {} ,遍历相关查询发现存在 LCA(T,4,6) ,且 vis[4] = true ,因此得到该查询的解为节点 4 所在集合的首领,即 LCA(T,4,6) = 1 ,该层递归结束。

递归返回,当前所在位置 u = 5 ,合并节点 6u 所在集合,并标记 vis[6] = true ,未遍历孩子集合 v = {} ,无相关查询因此该层递归结束。

递归返回,当前所在位置 u = 1 ,合并节点 5u 所在集合,并标记 vis[5] = true ,未遍历孩子集合 v = {} ,遍历相关查询发现存在 LCA(T,2,1) ,此时该查询的解便是节点 2 所在集合的首领,即 LCA(T,2,1) = 1 ,递归结束。

至此整个Tarjan算法便结束了

总结篇

对于不同的LCA问题我们可以选择不同的算法。

假若一棵树存在动态更新,此时离线算法就显得有点力不从心了,但是在其他情况下,离线算法往往效率更高。

另外, LCARMQ 问题是两个非常基础的问题,很多复杂问题都可以转化为这两类问题来解决。(当然这两类问题之间也可以相互转化)

『图论』LCA最近公共祖先的更多相关文章

  1. 『图论』LCA 最近公共祖先

    概述篇 LCA (Least Common Ancestors) ,即最近公共祖先,是指这样的一个问题:在一棵有根树中,找出某两个节点 u 和 v 最近的公共祖先. LCA 可分为在线算法与离线算法 ...

  2. lca 最近公共祖先

    http://poj.org/problem?id=1330 #include<cstdio> #include<cstring> #include<algorithm& ...

  3. Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载)

    Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载) 转载自:http://hi.baidu.com/lydrainbowcat/blog/item/2 ...

  4. LCA(最近公共祖先)模板

    Tarjan版本 /* gyt Live up to every day */ #pragma comment(linker,"/STACK:1024000000,1024000000&qu ...

  5. CodeVs.1036 商务旅行 ( LCA 最近公共祖先 )

    CodeVs.1036 商务旅行 ( LCA 最近公共祖先 ) 题意分析 某首都城市的商人要经常到各城镇去做生意,他们按自己的路线去做,目的是为了更好的节约时间. 假设有N个城镇,首都编号为1,商人从 ...

  6. LCA近期公共祖先

    LCA近期公共祖先 该分析转之:http://kmplayer.iteye.com/blog/604518 1,并查集+dfs 对整个树进行深度优先遍历.并在遍历的过程中不断地把一些眼下可能查询到的而 ...

  7. LCA 近期公共祖先 小结

    LCA 近期公共祖先 小结 以poj 1330为例.对LCA的3种经常使用的算法进行介绍,分别为 1. 离线tarjan 2. 基于倍增法的LCA 3. 基于RMQ的LCA 1. 离线tarjan / ...

  8. 【图论算法】LCA最近公共祖先问题

    LCA模板题https://www.luogu.com.cn/problem/P3379题意理解 对于有根树T的两个结点u.v,最近公共祖先LCA(u,v)表示一个结点x,满足x是u.v的祖先且x的深 ...

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

    对于一类题目,是一棵树或者森林,有多次查询,求2点间的距离,可以用LCA来解决.     这一类的问题有2中解决方法.第一种就是tarjan的离线算法,还有一中是基于ST算法的在线算法.复杂度都是O( ...

随机推荐

  1. 关于未来实现API管理系统的几个关键词

    下面将通过几个关键词的形式说明API管理的重要性和未来的实现方式. 1.生命周期管理 在整个API生命周期中更深入地集成所有工具将进一步提高生命周期循环的速度,而且更重要的是提供满足消费者需求的API ...

  2. Tomcat7.0.40注册到服务启动报错error Code 1 +connector attribute sslcertificateFile must be defined when using ssl with apr

    Tomcat7.0.40 注册到服务启动遇到以下几个问题: 1.启动报错errorCode1 查看日志如下图: 解决办法: 这个是因为我的jdk版本问题,因为电脑是64位,安装的jdk是32位的所以会 ...

  3. B-概率论-常见的概率分布模型

    目录 常见的概率分布模型 一.离散概率分布函数 二.连续概率分布函数 三.联合分布函数 四.多项分布(Multinomial Distribution) 4.1 多项分布简介 4.2 多项分布公式解析 ...

  4. linux系统下使用宝塔面板安装owncloud常见问题

    在安装owncloud时出现 无法写入“config”目录! 解决方法 在宝塔面板,找到owncloud根目录,点击"权限“设置权限 将权限设置为777,应用到子目录打勾(如下图) 确定后再 ...

  5. 客户端 SOCKET 编程

    建立客户端的 Socket: 客户端应用程序首先也是调用 WSAStartup() 函数来初始化 Winsock 的动态连接库,然后同样 调用 socket() 来建立一个 TCP 或 UDP Soc ...

  6. 域渗透基础之Windows 2012创建域控制器

    创建备份域控制器 这里就拿windows 2012 R2来当备份域控 如果一个域内有多个域控制器,可以有如下好处. 提高用户登录的效率:如果同时有多台域控制器对客户提供服务,可以分担审核用户登录身份( ...

  7. Python开发【第四篇】语句与函数

    语句 statement 语句是由一些表达式组成,通常一条语句可以独立的执行来完成一部分事情,并且形成结果. 多条语句写在一行内要用分号分开 例子: print('hello world') #这是一 ...

  8. Windows突破远程连接最大数去掉限制登录

    当对方设置最大连接数 超过限制时 可以用这个命令 win+r  输入 mstsc /v:192.168.18.131:3389 /console   windows server 2003 sp2 以 ...

  9. xpath相关用法及技巧

    本节讲解网页解析神器----XPath lxml下载 xpath基本用法 xpath插件 Xpath及XML路径语言,它是一门在XML文档查找信息的语言. 一:lxml下载以及安装 首先需要解决lxm ...

  10. ESP8266开发之旅 网络篇⑤ Scan WiFi——ESP8266WiFiScan库的使用

    1. 前言     现在,通常,为了让手机连上一个WiFi热点,基本上都是打开手机设置里面的WiFi设置功能,然后会看到里面有个WiFi热点列表,然后选择你要的连接上. 基本上你只要打开手机连接WiF ...