本文来自:http://www.cnblogs.com/Findxiaoxun/p/3428516.html

写得很好,一看就懂了。

在这里就复制了一份。

LCA问题:

给出一棵有根树T,对于任意两个结点u,v求出LCA(T, u, v),即离根最远的结点x,使得x同时是u和v的祖先。

把LCA问题看成询问式的:给出一系列询问,程序应当对每一个询问尽快做出反应。

对于这类问题有两种解决方法;一是用比较长的时间做预处理,但是等信息充足以后每次回答询问只需要用比较少的时间。这样的算法叫做在线算法。

另外有一类算法是先把所有的询问读入,然后一起把所有询问回答完成,这样的算法叫做离线算法。它们解决的问题都是询问式的,但是方法和特点不同,而且适用范围也不同(如果询问给出是有间隔的,往往只能用在线算法)。希望读者通过LCA问题能够对两类算法的基本设计方法有一个粗略的了解。

最简单的在线算法是先对所有可能的O(n2)种询问计算出结果,然后每次询问都可以在O(1)的时间内直接得到结果。

可以把它转化为O(n2)次单个的 LCA计算(实际上它已经是和离线算法一样了)。

每次可以用如下方法:

单个LCA问题的朴素算法:从u的父亲开始顺着树往上枚举u的祖先并保存在一个列表L中,然后再用类似的方法枚举v,当第一次发现某个祖先x在L中,则输出x.

由于L可以达到O(n)的,所以朴素算法的时间复杂度下限为O(n)。用此法的在线算法的时间复杂度下限为Ω(n3)算法可以通过递推来改进。

在线LCA问题的算法:令L(u)为u的深度(离根的距离)。不妨设L(u)<=L(v),则如果u是v的父亲,LCA(u,v)=u;否则LCA(u,v) = LCA(u, father(v)).

这样递推的总时间复杂度为O(n2)即在O(n2)的预处理,O(1)的询问时间解决了LCA问题。如果一个在线算法的预处理时间复杂度为O(f(n)),询问时间为O(g(n)),则用O(f(n))-O(g(n))来表示它。

刚才的递推方法给了我们一个启发。当L(u)<=L(v)时,可以根据LCA(u,v)的答案把所有结点分成若干个等价类

1.u的子树上结点v,都满足LCA(u, v)=u;

2.u父亲father(u)的任何不以u为根的子树上结点v都满足LCA(u, v) = father(u);

3.father(father(u))的任何不以father(u)为根的子树上结点v都满足LCA(u,v)=father(father(u))...

这个思路给我们提供了一个不错的离线算法。!!!

请仔细阅读分类这一部分,以上的内容都是lrj的黑书上的,如果看完文章后,仍疑惑,请再次阅读此段!!!

LCA的离线算法

个人认为,之所以离线算法比在线算法时间效率高,主要就是因为离线算法是先存储了查询,然后相当于将查询以一种有序的方式做了安排,而且,是边处理边查询,大大节省了时间。

利用递归的LCA过程。当lca(u)执行完毕后,以u为根的子树已经全部并为了一个集合。而一个lca的内部实际上做了的事就是对其子结点,依 此调用lca。当v1(第一个子结点)被lca,正在处理v2的时候,以v1为根的子树+u同在一个集合里,f(u)+编号比u小的u的兄弟的子树 同在 一个集合里,f(f(u)) + 编号比f(u)小的 f(u)的兄弟 的子树 同在一个集合里…… 而这些集合,对于v2的LCA都是不同的。因此只要 查询x在哪一个集合里,就能知道LCA(v2,x)

还有一种可能,x不在任何集合里。当他是v2的儿子,v3,v4等子树或编号比u大的u的兄弟的子树(等等)时,就会发生这种情况。即还没有被处理。还没有处理过的怎么办?把一个查询(x1,x2)往查询列表里添加两次,一次添加到x1的列表里,一次添加到x2的列表里,如果在做x1的时候发现 x2已经被处理了,那就接受这个询问。(两次中必定只有一次询问被接受)

void LCA(u){
for(u的每个儿子v){
LCA(v);
union(u,v);
}
visit[u]=;
for(查询中u的每个儿子v){
if(visit[v])
u,v的最近公共祖先是father[getfather[v]];
}
}

这里的union就是并查集中常用的union,即把v并到u的集合里,代表元是u。visit是标记数组,1表示之前已经访问过这个节点了,即,这个点的子树都已经被LCA了。father数组即是并查集的father数组,

getfather是含路径压缩的。

接下来以一个实例来解释这个算法:

求下面每对点的最近公共祖先

(1 5) (1 4) (4 2) (2 3) (1 3) (4 3)

这是一个普通的二叉树,我们先通过记录入度找到入度为0的节点找到root。然后LCA(root):

root = 5;

对于5的每个儿子,LCA,先LCA(1),1没有儿子,跳过第一个for,然后,visit[1]=true;查询的vector中(用vector来记录这个树和查询比较好,空间效率比较高),有一组(1,5),可是visit[5]现在仍然是初始值false;

然后,退出1的LCA,回到5的LCA,此时,进行5的第二个儿子4,4没有儿子,跳过第一个for,然后visit[4]=true;查询中有三组与4有关的,而只有1被访问过了,那么,ancestor(1,4)=father[getfather[1]]=5

退出4的LCA,回到5的,进行5的第三个儿子2,2有一个儿子,进入3的LCA;

3没有儿子,visit[3]=true;然后有三组查询与3有关,其中,1,4都访问过了,注意,2还没有访问,因为我们进入了LCA(2)的第一个for循环,而且,1,4此时的祖先都是5,那么,ancestor(3,1)=5;ancestor(3,4)=5;注意,此时,3的祖先仍是初始的他自己,如果1还有儿子,而查询的是1的儿子和3的话,1的儿子会被路径压缩,其祖先变成1的祖先5;

退出3的LCA,回到2的,而且把3union到2上了,visit[2]=true;查询中有两组记录与2有关,而且都已经访问过了,那么,也很同之前一样,得出结果。

退出2的LCA,visit[5]=true;还有一个关于5的查询,不再赘述。

程序结束。

LCA(最近公共祖先)离线算法Tarjan+并查集的更多相关文章

  1. POJ 1330 LCA最近公共祖先 离线tarjan算法

    题意要求一棵树上,两个点的最近公共祖先 即LCA 现学了一下LCA-Tarjan算法,还挺好理解的,这是个离线的算法,先把询问存贮起来,在一遍dfs过程中,找到了对应的询问点,即可输出 原理用了并查集 ...

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

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

  3. LCA(最近公共祖先)算法

    参考博客:https://blog.csdn.net/my_sunshine26/article/details/72717112 首先看一下定义,来自于百度百科 LCA(Lowest Common ...

  4. 求LCA最近公共祖先的在线ST算法_C++

    ST算法是求最近公共祖先的一种 在线 算法,基于RMQ算法,本代码用双链树存树 预处理的时间复杂度是 O(nlog2n)   查询时间是 O(1) 的 另附上离线算法 Tarjan 的链接: http ...

  5. 《程序员代码面试指南》第三章 二叉树问题 Tarjan算法与并查集解决二叉树节点间最近公共祖先的批量查询问题

    题目待续.... Tarjan算法与并查集解决二叉树节点间最近公共祖先的批量查询问题 java代码

  6. LCA 近期公共祖先 小结

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

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

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

  8. LCA近期公共祖先

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

  9. lca 最近公共祖先

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

随机推荐

  1. StyleCop学习笔记——自定义规则

    本文将简单的一步一步的指导这可能有助于学习如何创建自己的规则 1.创建一个项目. Visual Studio创建一个新的类库项目.NET3.5 2.引用两个DLL,StyleCop.dll和Style ...

  2. SSIS包配置动态配置数据库连接

    动态连接数据库便于维护 用SSIS包配置实现 1.控制流页签 - 右键 - 包配置 2.配置xml文件 3.指定连接属性:ServerName.UserName.Password 测试: 1.配置错误 ...

  3. Oracle出现字符集问题处理方法

    1.  Cmd进去DOS 2.  再输入dbca(database  create) 3.  弹出的界面,直接下一步,选择删除数据库 4.  成功删除后,回到一第一界面,选择创建数据库,下一步. 5. ...

  4. 九度oj 1407 快速找出最小数

    原题链接:http://ac.jobdu.com/problem.php?pid=1407 线段树,区间更新,查询区间最小值. 注意区间更新,查询的时候,区间$\begin{align*}[L,R] ...

  5. 编写可维护的JavaScript之简易模版

    /* * 正则替换%s * @para arg1(text) 需要替换的模版 * @para arg2 替换第一处%s * @para arg3 替换第二处%s * 返回替换后的字符串 */ var ...

  6. Windows Phone自定义控件 ProgressRing

    前言 Windows Phone为开发者提供了很多原生控件,但在很多场景下我们需要对默认的功能或样式做一定的修改才能满足我们的需求,自定义控件应运而生.本文通过以自定义控件进度环(ProgressRi ...

  7. trap命令使用

    分享一个shell脚本技巧,大家写shell脚本的时候,一般而言仅仅保证功能可用,但程序的鲁棒性却不是太好,不够健壮,多数是脚本处理 一些中断信号导致,应对非预期的系统信号,其实系统自带的trap命令 ...

  8. QT 的信号与槽

    转载: QT 的信号与槽机制介绍 QT 是一个跨平台的 C++ GUI 应用构架,它提供了丰富的窗口部件集,具有面向对象.易于扩展.真正的组件编程等特点,更为引人注目的是目前 Linux 上最为流行的 ...

  9. Android Jni变量对照表

    字符 Java类型 C类型 V      void            void Z       jboolean     boolean I        jint              in ...

  10. PE格式的理解(待补充)

    PE文件格式 一.基本结构 1.DOS头一般到节区头成为PE头部分,其下称为PE体.文件的内容一般可分为代码(.text).数据(.data).资源(.rsrc),分别保存. 2.PE头与各节区的尾部 ...