LCA(最近公共祖先)问题
1.这个算法基于并查集和深度优先搜索。算法从根开始,对每一棵子树进行深度优先搜索,访问根时,将创建由根结点构建的集合,然后对以他的孩子结点为根的子树进行搜索,使对于 u, v 属于其某一棵子树的 LCA 询问完成。这时将其所有子树结点与根结点合并为一个集合。 对于属于这个集合的结点 u, v 其 LCA 必定是根结点。
2对于最近公共祖先问题,我们先来看这样一个性质,当两个节点(u,v)的最近公共祖先是x时,那么我们可以确定的说,当进行后序遍历的时候,必然先访问完x的所有子树,然后才会返回到x所在的节点。这个性质就是我们使用Tarjan算法解决最近公共祖先问题的核心思想。
同时我们会想这个怎么能够保证是最近的公共祖先呢?我们这样看,因为我们是逐渐向上回溯的,所以我们每次访问完某个节点x的一棵子树,我们就将该子树所有节点放进该节点x所在的集合,并且我们设置这个集合所有元素的祖先是该节点x。那么到我们完成对一个节点的所有子树的访问时,我们将这个节点标记为已经找到了祖先的点。
这个时候就体现了Tarjan采用离线的方式解决最近公共祖先的问题特点所在了,所以这个时候就体现了这一点。假设我们刚刚已经完成访问的节点是a,那么我们看与其一同被询问的另外一个点b是否已经被访问过了,若已经被访问过了,那么这个时候最近公共祖先必然是b所在集合对应的祖先c,因为我们对a的访问就是从最近公共祖先c转过来的,并且在从c的子树b转向a的时候,我们已经将b的祖先置为了c,同时这个c也是a的祖先,那么c必然是a、b的最近公共祖先。
对于一棵子树所有节点,祖先都是该子树的根节点,所以我们在回溯的时候,时常要更新整个子树的祖先,为了方便处理,我们使用并查集维护一个集合的祖先。总的时间复杂度是O(n+q)的,因为dfs是O(n)的,然后对于询问的处理大概就是O(q)的。
http://poetrinity.diandian.com/post/2012-02-04/19684915
LCA(最近公共祖先)问题
方法一:Tarjan离线算法
在学习离线算法的时候先需要先巩固一下深度搜索,并查集
Tarjan离线算法是基于深度优先搜索的,我们从根开始向下搜索,搜到一个节点的时候首先判断该节点所有子节点是否访问过,如果都已经访问过,则判断该节点是否询问点里面的其中一个,如果是,则判断跟它相对应的那个点是否已经访问过,如果访问过,则他们的最近公共祖先便是已经访问过的那个节点的当前节点,如果另外一个节点没有访问,则继续进行深度搜索。
例题:POJ 1330 Nearest Common Ancestors
这道题目是让求解最近公共祖先的问题,不过这道题目让我们求解的只有一组,对于离线算法而言,可以一下子求解多组,只要我们把需要求解的所有组建立一张表,当搜索到这个节点的时候去寻找跟这个节点相连的节点有没有被访问过的,如果访问过,则将他们的公共祖先先记录下来,这样到最后搜索完所有的点后就可以在O(1)的时间内输出所有需要求解最近公共祖先的解了。
#include #include using namespace std;const int size=10005; int T,N; vectorvec[size]; int father[size]; bool root[size]; bool visited[size]; int first,second;void In_data() {scanf("%d",&N);for(int i=1;i<=N;++i){vec[i].clear();father[i]=i;root[i]=true;visited[i]=false;}for(int i=1;i {int beg,end; scanf("%d%d",&beg,&end);vec[beg].push_back(end);root[end]=false;}scanf("%d%d",&first,&second); //记录两个节点 }int Find(int x) {if(father[x]==x)return x;else return father[x]=Find(father[x]); }void Union(int a,int b) {int A=Find(a);int B=Find(b);if(A!=B)father[B]=A; }void LCA(int parent) {for(int i=0;i {LCA(vec[parent][i]);Union(parent,vec[parent][i]);}visited[parent]=true; //确认该点已经搜索过//如果目前的访问的节点就是其中一个节点,查看两外一个是否访问过if(parent==first&&visited[second]==true) {printf("%d\n",Find(second));return ;}else if(parent==second&&visited[first]==true){printf("%d\n",Find(first));return ;} }int main() {//freopen("1.txt","r",stdin);scanf("%d",&T);while(T--){In_data();//以根节点为入口for(int i=1;i<=N;++i) {if(root[i]==true){LCA(i);break;}}} }
方法二:在线算法
第二种求解最近公共祖先就是采用在线算法,这种算法可以融入ST算法来加快速度,在这儿先以一种简单的方法来对在线算法有个初步的认识。
在线算法首先也是给给出的边来建立一个图,找出根节点,然后深度搜索每一个节点,求出每一个节点的深度(根节点深度可以赋值为0),然后我们找到要求的那对点的深度,如果两者深度不一样,先让深度大的网上移动,直到两者深度一样,此时判断两者是否为同一个节点,如果不是同一个节点,就都将他们改变为他们的父节点,这样一步一步往上移动,最后当遇到两者一样的时候,这个点就是它们的根节点了。
同样是上面一道例题,在线算法的代码如下
#include #include using namespace std;const int size=10005; int T,N,first,second; vectorvec[size]; //用来记录图的信息 bool root[size]; //用来查找根节点 int depth[size]; //用来记录该节点的深度 int father[size]; //用来记录该节点的父节点//录入数据 void In_data() {scanf("%d",&N);for(int i=1;i<=N;++i){vec[i].clear();depth[i]=0;root[i]=true;father[i]=i;}for(int i=1;i {int beg,end; scanf("%d%d",&beg,&end);vec[beg].push_back(end);father[end]=beg;root[end]=false;}scanf("%d%d",&first,&second); }//parent表示根节点,dep表示当前节点的深度 void depth_plus(int parent,int dep) {for(int i=0;i {depth_plus(vec[parent][i],dep+1);}depth[parent]=dep; }//在线算法查找最近公共祖先 int find_ancestor() {if(depth[first]>depth[second]){while(depth[first]!=depth[second]){first=father[first];}}else if(depth[second]>depth[first]){while(depth[first]!=depth[second]){second=father[second];}}while(first!=second){first=father[first];second=father[second];}return first; }int main() {//freopen("1.txt","r",stdin);scanf("%d",&T);while(T--){In_data();//以根节点为入口,给每个点赋予深度for(int i=1;i<=N;++i){if(root[i]==true){depth_plus(i,0);break;}}printf("%d\n",find_ancestor());} }
采取上面一步一步往上推的在线算法是比较山寨的,时间可能比较慢点儿,所以我们就可以采用ST算法来对上面的方法进行一些预处理。当然有不好的地方必然也有点儿小优点。问题:现在给一个树添加一条边,你能求出这个树里面所新形成的环吗?输出这个环的各条边。对于这个问题我们就可以采用上面一步一步的向上搜索的方法,这样最后就能输出这个环了。当然要按问题讨论的,我们还是应该学一下ST算法来预处理下加速的。
LCA(最近公共祖先)问题相关推荐
- Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)...
转载自:http://hi.baidu.com/lydrainbowcat/blog/item/2194090a96bbed2db1351de8.html 基本概念: 1.割点:若删掉某点后,原连通图 ...
- LCA 最近公共祖先(RMQ、树上倍增、Tarjan),树上两点距离,线段重合长度
对于LCA的一些理解 RMQ dfs处理树 对于一个树形结构,可以用dfs将一颗树转化成数组,数组中记录每个点的标号,这样数组就按照dfs的顺序把树存了下来 确定祖先的范围 对于询问的节点X和Y, X ...
- POJ 1330 LCA最近公共祖先 离线tarjan算法
题意要求一棵树上,两个点的最近公共祖先 即LCA 现学了一下LCA-Tarjan算法,还挺好理解的,这是个离线的算法,先把询问存贮起来,在一遍dfs过程中,找到了对应的询问点,即可输出 原理用了并查集 ...
- LCA(最近公共祖先)(leetcode 236 python C++)
LCA(Lowest Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先. # Definition for a binary tree node. ...
- LCA 最近公共祖先
LCA ,也就是最近公共祖先是什么意思呢. 下面这张图可能会让你清楚的明白什么是最近公共祖先. 对于初始点,前提是它能构成一棵不成环的树,之所以不能成环,从定义就看出来了嘛,如果成环,是不是有种1是3 ...
- 故事篇之 LCA 最近公共祖先(一)
故事出真知 阿珍 爱上了 阿强 但 被他们的父母 拒绝 了 没错 狗血的近亲结婚剧情开始了 阿珍 说: 不行,我深深爱着我的阿强,谁也不能把我们分开 阿强 深情地望着阿珍 说 一定会有机会的! 此时 ...
- 模板 - LCA最近公共祖先(倍增法、Tarjan、树上差分、LCA优化的次小生成树)
整理的算法模板合集: ACM模板 注意x和y的LCA可以是x或者y本身 一.LCA的在线倍增算法 /*给定一棵包含 n个节点的有根无向树,有 m个询问,每个询问 给出了一对节点的编号 x和 y,询问 ...
- LCA 最近公共祖先 详解
一.内容 给定一棵包含 n 个节点的有根无向树,节点编号互不相同,但不一定是 1∼n.有 m 个询问,每个询问给出了一对节点的编号 x 和 y,询问 x 与 y 的祖孙关系.输入格式 输入第一行包括一 ...
- LeetCode Lowest Common Ancestor of a Binary Search Tree (LCA最近公共祖先)
题意: 给一棵二叉排序树,找p和q的LCA. 思路: 给的是BST(无相同节点),那么每个节点肯定大于左子树中的最大,小于右子树种的最小.根据这个特性,找LCA就简单多了. 分三种情况: (1)p和q ...
最新文章
- 服务器搭建虚拟win云服务,云服务器创建win10虚拟机
- SpringMVC 处理multipart形式数据:java方式配置文件上传
- 基于词典的正向最大匹配中文分词算法,能实现中英文数字混合分词
- 鸿蒙系统画饼,任正非说在三年内华为鸿蒙系统即可媲美苹果!真的不是“画饼”?...
- 机房收费--上机状态查询
- Mybatis接口Mapper内的方法为啥不能重载吗?
- 反素数(luogu 1463)
- python下载显示文件丢失_Microsoft.PythonTools.resources.dll
- php http请求xml数据,使用php从HTTP POST请求中检索XML
- oracle统计学生成绩c,Oracle11g学生成绩管理系统.docx
- python例题代码_python26个练习题及代码-789
- NIO-java.nio.ByteBuffer中flip、rewind、clear方法的区别
- 关于触控 ID 的妙控键盘上无法正常使用触控 ID的解决方法
- 电脑硬盘怎么测试软件,HD Tune pro硬盘检测工具怎么用
- JavaScript中的倒叙和排序
- 粗糙集(Rough Sets)
- 为什么要学数学--读《给讨厌数学的人》第二章之读书笔记
- 笔试强训48天——day29
- 基于jquery的轮播图组件开发-1
- 201871010104-陈园园 《面向对象程序设计(java)》第二周学习总结