当前位置: 首页 > news >正文

2019/8/1 LCA(最近公共祖先) (2)

   LCA(最近公共祖先)

     LCA,Lowest Common Ancetors,即最近公共祖先。

  有关LCA定义及示例详见上一篇LCA(RMQ)。

  今天向大家介绍的是LCA(Tarjan)算法

  Tarjan求解LCA

  前置知识

  • 并查集

  • 链式前向星

  • DFS

  梗概:Tarjan求解LCA是利用DFS的性质在对给定树进行遍历时不断更新一给定询问结点在目前遍历高度的祖先,当遍历到另一询问节点时查找前一询问节点祖先,以此求出给定两点LCA。

  时间复杂度:O(n+q)     q:询问次数

  1.在线/离线算法

  在线算法:

“在计算机科学中,一个在线算法是指它可以以序列化的方式一个个的处理输入,也就是说在开始时并不需要已经知道所有的输入。”------百度百科

  离线算法:

  “算法设计策略都是基于在执行算法前输入数据已知的基本假设,也就是说,对于一个离线算法,在开始时就需要知道问题的所有输入数据,而且在解决一个问题后就要立即输出结果,通常将这类具有问题完全信息前提下设计出的算法称为离线算法( off line algorithms)”------百度百科

  本文介绍的Tarjan求解LCA算法是一种离线算法,其需要将询问储存起来集中处理,得到答案后一并输出。

  2.Tarjan

  “Robert Tarjan,计算机科学家,以LCA、强连通分量等算法闻名。”------百度百科

  Tarjan(求LCA)算法基于DFS原理。利用LCA必然存在于从一询问节点至另一询问结点的DFS遍历路径上的性质,回答询问。

  又是这棵树(再次手残)

                                                              

              给定树                                                                                  访问路径                                                                    “询问”树

  首先,读入整颗给定树,建树,读入所有询问(3 11),建立“询问树”。

  对于询问结点3与结点11,假定我们按照顺序遍历了除结点11外结点7的子树中所有结点。

  示例流程(具体代码见“实现”部分):    

        1.将结点11标记为已访问。

        2.枚举结点11所有出边。

        3.由于结点11无未访问的出边指向的点,开始遍历结点11在“询问”树中所有的出边。

        4.出边指向结点3,由于节点3未访问,直接跳过。

        5.回溯至结点9,继续访问结点9的出边,发现遍历完成,更新f[11]=9,回溯。

        6.重复第5步直至访问结点2,更新f[7]=2,继续遍历结点2出边,访问未遍历节点3。

        7.重复第1至5步,直至遍历完结点3的所有出边。

        8.结点3无未遍历出边,开始遍历结点3在“询问树”中的出边。

        9.出边指向已访问节点11,查找结点11的祖先。

        10.由于还未回溯至结点1,所以f[2]还未更新为1,此时仍为2,即f[11]=f[9]=f[7]=f[2]=2。

        11.结点2即为询问(3 11)的LCA。

  以上即是此种算法求LCA的基本步骤,在实现时,可归纳为

        1.标记结点为已访问。

        2.遍历所有出边,如指向结点为访问则继续递归。

        3.回溯,更新父亲节点。(基于并查集)

        4.遍历结点在“询问”树中所有出边,如指向结点已访问,存储指向结点祖先为此次询问结果。

  至此,原理分析结束。(如仍有疑问请看“实现”部分)

  3.实现

  代码:

  声明部分:

1 #include <iostream>
2 #include <cstdio>//标准输入输出 
3 using namespace std;
4 int n,m,s,cnt;//cnt为边计数器 
5 int head[500005];//链式前向星 
6 int qhead[500005];//链式前向星 (询问) 
7 int f[500005];//并查集储存父结点
8 int vis[500005];//判断是否访问过 
9 int lca[1000005];//离线存储答案 

  链式前向星部分:

 1 struct edge
 2 {
 3     int nxt;
 4     int to;
 5     //int dis;边权值默认为 1 
 6 }e[1000005];//建议4倍 
 7 
 8 struct qedge
 9 {
10     int nxt;
11     int to;
12     //int dis;
13 }qe[1000005];//建议4倍 
14 
15 void add(int x,int y/*,int d*/)
16 {
17     e[++cnt].nxt=head[x];
18     e[cnt].to=y;
19     head[x]=cnt;
20 }//链式前向星加边 
21 
22 void qadd(int x,int y)
23 {
24     qe[++cnt].nxt=qhead[x];
25     qe[cnt].to=y;
26     qhead[x]=cnt;
27 }//链式前向星加边 (询问) 

  并查集模板部分:

 1 int find(int x)
 2 {
 3     return f[x]== x ? x : f[x] = find( f[x] );//路径压缩 
 4 }//“查 ” 
 5 
 6 void merge(int a,int b)
 7 {
 8     int fa=find(a),fb=find(b);
 9     if(fa==fb)
10     return ;
11     f[fa]=fb;
12 }//“并 ” 
13 
14 void setup(int N)
15 {
16     for(int i=1;i<=N;i++)
17     {
18         f[i]=i;
19     }
20 }//初始化 

 

  Tarjan(DFS)函数部分:

 1 void tarjan(int x)
 2 {
 3     cout<<"#访问节点:"<<x<<endl;
 4     vis[x]=1;//标记访问 
 5     for(int i=head[x];i;i=e[i].nxt)//遍历此子节点所有出边 
 6     {
 7         int v=e[i].to;
 8         if(!vis[v])//如下一节点未访问 
 9         {
10             tarjan(v);//继续遍历 
11             f[v]=x;//回溯时更新子节点祖先 
12             if(v==11)
13             {
14                 cout<<"//"<<x<<" "<<f[11]<<endl;
15             }
16         }
17     }
18     for(int i=qhead[x];i;i=qe[i].nxt)//对于询问中有此点的询问组 
19     {
20         int qv=qe[i].to;
21         if(vis[qv])//若另一询问节点已被遍历 
22         {
23             lca[i]=find(qv);//则对于这组询问,其LCA为另一询问节点最后一次更新的祖先 
24             if(i%2)//对于正反两组相同询问  
25                 lca[i+1]=lca[i];//若为正向的询问,下一组询问为此次询问的反向询问 ,询问结果相同 
26             else
27                 lca[i-1]=lca[i];//若为反向的询问,下一组询问为此次询问的正向询问 ,询问结果相同 
28         }
29     }
30 }//DFS函数 

  主函数:

 1 int main()
 2 {
 3     cin>>n>>m>>s;
 4     for(int i=1;i<n;i++)
 5     {
 6         int a,b;
 7         cin>>a>>b;
 8         add(a,b);//无向图正反存边 
 9         add(b,a);
10     }
11     cnt=0;//懒得再加一个变量qcnt 
12     for(int i=1;i<=m;i++)
13     {
14         int a,b;
15         cin>>a>>b;
16         qadd(a,b);//储存正反询问 
17         qadd(b,a);//无向图正反存边 
18     }
19     setup(n);//初始化 
20     tarjan(s);//以s为根开始遍历 
21     for(int i=1;i<=m;i++)
22     {
23         printf("%d\n",lca[i*2]);//由于每组询问储存了两遍,所以输出时输出其中一组 
24     }
25     return 0;
26 }

  结语:

  Tarjan好啊!!!

  相关题目:

  见上一篇末。

  //还没想起来······· 

  over.


  

转载于:https://www.cnblogs.com/randomaddress/p/11282918.html

相关文章:

  • Time.realtimeSinceStartup——秒秒秒单位
  • 成都边锋 云端虚拟化工具 系统驱动层 原理初窥
  • SpringBoot入门最详细教程
  • unreal——控制器
  • flex与js交互浅析
  • python安装pip以及使用pip安装requests等模块
  • 使用luac加密lua文件
  • mac安装android studio+NDK
  • matrix(DP杂题,思维题)
  • xlua的加固
  • 架构模式: 无服务器部署
  • persistence配置
  • xlua崩溃
  • lua编码:unexpected symbol near ‘<\239>‘
  • Windows Vista下的远程桌面连接测试
  • hexo+github搭建个人博客
  • -------------------- 第二讲-------- 第一节------在此给出链表的基本操作
  • [译] 理解数组在 PHP 内部的实现(给PHP开发者的PHP源码-第四部分)
  • Android框架之Volley
  • EventListener原理
  • HTTP 简介
  • java小心机(3)| 浅析finalize()
  • Nacos系列:Nacos的Java SDK使用
  • node-glob通配符
  • tab.js分享及浏览器兼容性问题汇总
  • unity如何实现一个固定宽度的orthagraphic相机
  • Webpack4 学习笔记 - 01:webpack的安装和简单配置
  • 阿里云容器服务区块链解决方案全新升级 支持Hyperledger Fabric v1.1
  • 基于webpack 的 vue 多页架构
  • 精彩代码 vue.js
  • 快速体验 Sentinel 集群限流功能,只需简单几步
  • 聊聊flink的TableFactory
  • 扫描识别控件Dynamic Web TWAIN v12.2发布,改进SSL证书
  • 推荐一个React的管理后台框架
  • 与 ConTeXt MkIV 官方文档的接驳
  • 智能合约开发环境搭建及Hello World合约
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • 《码出高效》学习笔记与书中错误记录
  • ​DB-Engines 12月数据库排名: PostgreSQL有望获得「2020年度数据库」荣誉?
  • ​一、什么是射频识别?二、射频识别系统组成及工作原理三、射频识别系统分类四、RFID与物联网​
  • # 安徽锐锋科技IDMS系统简介
  • (pytorch进阶之路)扩散概率模型
  • (附源码)spring boot建达集团公司平台 毕业设计 141538
  • (附源码)spring boot校园健康监测管理系统 毕业设计 151047
  • (附源码)springboot猪场管理系统 毕业设计 160901
  • (附源码)计算机毕业设计SSM疫情居家隔离服务系统
  • (力扣记录)235. 二叉搜索树的最近公共祖先
  • (数位dp) 算法竞赛入门到进阶 书本题集
  • .bat批处理(十一):替换字符串中包含百分号%的子串
  • .NET / MSBuild 扩展编译时什么时候用 BeforeTargets / AfterTargets 什么时候用 DependsOnTargets?
  • .NET WebClient 类下载部分文件会错误?可能是解压缩的锅
  • .Net通用分页类(存储过程分页版,可以选择页码的显示样式,且有中英选择)
  • .NET与 java通用的3DES加密解密方法
  • .net与java建立WebService再互相调用
  • /usr/bin/python: can't decompress data; zlib not available 的异常处理