详解使用 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 需要用到并查集,以下是本人使用的并查集模板。
int fa[100000];
void reset(){
for (int i=1;i<=100000;i++){
fa[i]=i;
}
}
int getfa(int x){
return fa[x]==x?x:getfa(fa[x]);
}
void marge(int x,int y){
fa[getfa(y)]=getfa(x);
}
由于 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)$(这个是并查集中的查找函数),输出或者记录下来就可以了。
这是伪代码
void tarjan(int x){
//在本代码段中,s[i]为第i个子节点 , t[i]为第i个和当前节点有查询关系的结点。
vis[x]=1;//标记已经访问,vis是记录是否已访问的数组
for (i=1;i<=子节点数;i++){//枚举子节点 (递归并合并)
tarjan(s[i]);
marge(x,s[i]);//并查集合并
}
for (i=1;i<=有查询关系的结点数;i++){
if (vis[t[i]]){
cout<<x<<"和"<<t[i]<<"的LCA是"<<getfa(t[i])<<endl;//如果t[i]已经访问了输出(getfa是并查集查找函数)
}
}
}
核心代码就这么一点?对,就这么一点。
如果你还不理解,那么可以跳转到最后一章看图解演示。
一些重要的细节
为了接下来的讲解,下面我们明确一下读入方式,不同的读入方式可以自己变通一下。
第一行两个数 n 和 q,表示结点数和查询数。
接下来 n 行每行两个数,表示左子结点和右子结点编号,如没有则是 -1。
接下来 q 行每行两个数,表示查询的两个结点编号。
例如上图的树,读入为
9 5
2 3
4 5
-1 6
-1 -1
7 8
-1 9
-1 -1
-1 -1
-1 -1
5 4
7 4
7 8
9 3
8 6
如何存储查询关系
我在这里用的方法是二维数组。
int t[100000][10],top[100000];
//t[i][j]表示编号为i的结点,第j个和它有查询关系的点的编号
//top[i]表示编号为i的结点与它有查询关系的点的数量
注意:需要双向存储关系。例如结点 2 和 3,不仅要更新t[2],还要更新t[3]。
读入代码长这样:
for (int i=1;i<=q;i++){
cin>>a[i]>>b[i];
t[a[i]][++top[a[i]]]=b[i];
t[b[i]][++top[b[i]]]=a[i];
}
当然如果你想要优化下空间那么把这个数组变成vector也是没问题的。
这就没了...
代码
直接输出的写法
#include<bits/stdc++.h>
using namespace std;
int n,k,q,v[100000];
map<pair<int,int>,int> ans;//存答案
int t[100000][10],top[100000];//存储查询关系
struct node{
int l,r;
};
node s[100000];
/*并查集*/
int fa[100000];
void reset(){
for (int i=1;i<=n;i++){
fa[i]=i;
}
}
int getfa(int x){
return fa[x]==x?x:getfa(fa[x]);
}
void marge(int x,int y){
fa[getfa(y)]=getfa(x);
}
/*------*/
void tarjan(int x){
v[x]=1;//标记已访问
node p=s[x];//获取当前结点结构体
if (p.l!=-1){
tarjan(p.l);
marge(x,p.l);
}
if (p.r!=-1){
tarjan(p.r);
marge(x,p.r);
}//分别对l和r结点进行操作
for (int i=1;i<=top[x];i++){
if (v[t[x][i]]){
cout<<getfa(t[x][i])<<endl;
}//输出
}
}
int main(){
cin>>n>>q;
for (int i=1;i<=n;i++){
cin>>s[i].l>>s[i].r;
}
for (int i=1;i<=q;i++){
int a,b;
cin>>a>>b;
t[a][++top[a]]=b;//存储查询关系
t[b][++top[b]]=a;
}
reset();//初始化并查集
tarjan(1);//tarjan 求 LCA
}
先记录而不输出的写法
#include<bits/stdc++.h>
using namespace std;
int n,k,q,v[100000];
map<pair<int,int>,int> ans;//存答案
int t[100000][10],top[100000];//存储查询关系
int a[100000],b[100000];
struct node{
int l,r;
};
node s[100000];
/*并查集*/
int fa[100000];
void reset(){
for (int i=1;i<=n;i++){
fa[i]=i;
}
}
int getfa(int x){
return fa[x]==x?x:getfa(fa[x]);
}
void marge(int x,int y){
fa[getfa(y)]=getfa(x);
}
/*------*/
void tarjan(int x){
v[x]=1;
node p=s[x];
if (p.l!=-1){
tarjan(p.l);
marge(x,p.l);
}
if (p.r!=-1){
tarjan(p.r);
marge(x,p.r);
}
for (int i=1;i<=top[x];i++){
if (v[t[x][i]]){
pair<int,int> tmp,tmp1;//用pair配合map来存储答案
tmp=make_pair(x,t[x][i]);
tmp1=make_pair(t[x][i],x);//两个pair的目的是例如3 2这种数据如果搜到3才有答案那么进时的顺序不止是(3,2),还有(2,3),方便输出结果时查询
ans[tmp]=getfa(t[x][i]);
ans[tmp1]=getfa(t[x][i]);
cout<<"#"<<ans[tmp]<<endl;
}
}
}
int main(){
cin>>n>>q;
for (int i=1;i<=n;i++){
cin>>s[i].l>>s[i].r;
}
for (int i=1;i<=q;i++){
cin>>a[i]>>b[i];
t[a[i]][++top[a[i]]]=b[i];
t[b[i]][++top[b[i]]]=a[i];
}
reset();
tarjan(1);
for (int i=1;i<=q;i++){
pair<int,int> tmp;
tmp=make_pair(b[i],a[i]);
cout<<a[i]<<"-"<<b[i]<<":"<<ans[tmp]<<endl;
}
}
算法演示
下一步 上一步
详解使用 Tarjan 求 LCA 问题(图解)的更多相关文章
- 【Tarjan】洛谷P3379 Tarjan求LCA
题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每 ...
- 倍增\ tarjan求lca
对于每个节点v,记录anc[v][k],表示从它向上走2k步后到达的节点(如果越过了根节点,那么anc[v][k]就是根节点). dfs函数对树进行的dfs,先求出anc[v][0],再利用anc[v ...
- Tarjan求LCA
LCA问题算是一类比较经典的树上的问题 做法比较多样 比如说暴力啊,倍增啊等等 今天在这里给大家讲一下tarjan算法! tarjan求LCA是一种稳定高速的算法 时间复杂度能做到预处理O(n + m ...
- 倍增 Tarjan 求LCA
...
- SPOJ 3978 Distance Query(tarjan求LCA)
The traffic network in a country consists of N cities (labeled with integers from 1 to N) and N-1 ro ...
- tarjan求lca的神奇
题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每 ...
- Tarjan求LCA(离线)
基本思想 把要求的点对保存下来,在dfs时顺带求出来. 方法 将每个已经遍历的点指向它回溯的最高节点(遍历它的子树时指向自己),每遍历到一个点就处理它存在的询问如果另一个点已经遍历,则lca就是另一个 ...
- 免费的HTML5连载来了《HTML5网页开发实例详解》连载(五)图解通过Fiddler加速开发
Fiddler是Windows底下最强大的请求代理调试工具,监控任何浏览器的HTTP/HTTPS流量,窜改客户端请求和服务器响应,解密HTTPS Web会话,图4.44为Fiddler原理示意图. 图 ...
- 算法详解之Tarjan
"tarjan陪伴强联通分量 生成树完成后思路才闪光 欧拉跑过的七桥古塘 让你 心驰神往"----<膜你抄> 一.tarjan求强连通分量 什么是强连通分量? 引用来自 ...
随机推荐
- Vue---从后台获取数据vue-resource的使用方法
作为前端人员,在开发过程中,我们大多数情况都需要从后台请求数据,那么在vue中怎样从后台获取数据呢?接下来,我简单介绍一下vue-resource的使用方法,希望对大家有帮助. 一.下载vue-res ...
- P3232 [HNOI2013]游走 解题报告
P3232 [HNOI2013]游走 题目描述 一个无向连通图,顶点从\(1\)编号到\(N\),边从\(1\)编号到\(M\). 小Z在该图上进行随机游走,初始时小Z在1号顶点,每一步小Z以相等的概 ...
- 【bzoj3529】 Sdoi2014—数表
μhttp://www.lydsy.com/JudgeOnline/problem.php?id=3529 (题目链接) 题意 多组询问,每次给出${n,m,a}$.求$${\sum_{i=1}^n\ ...
- JS中的new操作符
在JS中定义一个构造函数,然后用new操作符构造对象obj,JS代码如下. function Base(){ this.name = "swf"; this.age =20; } ...
- vue中assets文件夹与static文件夹的区别
1.如果这些产品图片文件“万年不变”,放在 /static 目录里,(不需要使用require将这些图片作为模块来引用) var products = [{ img: '/static/img/pro ...
- oracle 工作笔记,不定期更新
此博客为工作时,所见技术问题的解决方案笔记,欢迎大家转载,转载请注明出处,谢谢~ 更新时间: 2017-07-12 1. clob字段值读取时,借用extractvalue或extract函数读取节点 ...
- React JSX基本语法规则
JSX基本语法规则: 遇到HTML(以 < 开头)标签,就用HTML规则解析: 遇到代码块(以 { 开头),就用JavaScript规则解析. 它允许HTML和JavaScript的混写. 注意 ...
- SQL记录-PLSQL条件控制
PL/SQL条件控制 决策结构需要程序员指定一个或多个条件要计算,或由程序进行测试,如果条件被确定为真那么一条或多条语句被执行,如果要被执行的其它语句条件被确定为假,则选其它执行块. 以下是从在大 ...
- windows10 升级1803后,远程错误提示“出现身份验证错误,要求的函数不受支持 CredSSP 加密 Oracle修正”的解决办法
远程出现错误提示:出现身份验证错误,要求的函数不受支持 CredSSP 加密 Oracle修正 运行 gpedit.msc 本地组策略: 计算机配置>管理模板>系统>凭据分配> ...
- 机器学习:分类算法性能指标之ROC曲线
在介绍ROC曲线之前,先说说混淆矩阵及两个公式,因为这是ROC曲线计算的基础. 1.混淆矩阵的例子(是否点击广告): 说明: TP:预测的结果跟实际结果一致,都点击了广告. FP:预测结果点击了,但是 ...