LCA问题第二弹

上次用二分的方法给大家分享了对 LCA 问题的处理,各位应该还能回忆起来上次的方法是由子节点向根节点(自下而上)的处理,平时我们遇到的很多问题都是正向思维处理困难而逆向思维处理比较容易,LCA问题也可以划分为这一类问题的范畴。那是不是就意味着 LCA 无法从正面思维中解决呢?当然不是的,只是要直接想到解决的办法需要耗费一些功夫。那今天咱们就从问题的正面来研究一下 LCA ,也就是说,今天我们采用由上而下的遍历方式处理 LCA问题,那今天我们的目的能够达到吗?且往下看。

由上而下势必要对树进行遍历,树的遍历大致分为四种类型:先序遍历(先根遍历)、中序遍历(中根遍历)、后序遍历(后根遍历)、层序遍历。

先序遍历:对于一棵(子)树,先遍历根节点,再遍历左子树,最后遍历右子树的遍历顺序。

中序遍历:对于一棵(子)树,先遍历左子树,再遍历根节点,最后遍历右子树的遍历顺序。

后序遍历:对于一棵(子)树,先遍历左子树,再遍历右子树,最后遍历根节点的遍历顺序。

层序遍历:对于一棵(子)树,按照树的层级结构对所有节点进行遍历。

如下图:

先序遍历序列为:A B D H P I Q T E J K R C F L S M G N O

中序遍历序列为:P H D I T Q B J E R K A L S F M C N G O

后序遍历序列为:P H T Q I D J R K E B S L M F N O G C A

层序遍历序列为:A B C D E F G H I J K L M N O P Q R S T

假设我们还是求 P 和 D 的最近公共祖先 D ,观察先序遍历、中序遍历、后序遍历的遍历路径和序列,我们很难整理出可以转化为代码实现的思路来,关键在于这三种遍历方式在对节点的访问上跳跃性太大,无法连续的访问整棵树,而这种跳跃性在序列上是没有表现出来的,因此很难利用。

再看层级遍历,虽然也不是连续的,但是其层级的特征十分明显,可以适当运用。提一个大家都想得到的实现方法:对层序遍历函数进行改写,加入检测函数用来检测当前节点是否就是最近公共祖先,当在某一子树中发现两个待求子节点时,将不必再遍历同级剩余未遍历的子树了。检测函数实现步骤核心如下:

以当前所在节点为根节点向子树进行遍历,若能够同时找到两个待求子节点,将当前节点更新为可能的最近公共祖先并访问下一级,如果下一级的任何一个子树都无法同时找到两个待求子节点,那么确定之前的最近公共祖先即为所求,如果下一级中存在某一子树同时包含两个待求子节点,更新此子树的根节点为可能的最近公共祖先,进行同上一级同样的处理,具体实现代码不做赘述。

由上面的描述来看,似乎 DFS 更适合来实现检测函数,因为在一个分支找到想要的结果之后不用再耗费时间确认其他分支,这样更节省时间。而如果所求子节点是叶节点, BFS 可能需要遍历所有的节点才能知道结果。

现在面临的问题只是求两个子节点的最近公共祖先节点,当问题的规模逐渐增大的时候,以上的方法显然捉襟见肘,因此我们需要深入分析。


上一个方法主要的时间耗费在对树的遍历上,如下图所示:

若要求 X 和 Y 的最近公共祖先,那不管是采用BFS的方法还是 DFS的方法,显然需要遍历的次数都远远难以接受。如果对于一棵树,进行 n 次遍历,待求子节点都在如图 X 和 Y 的位置,那样的复杂程度更是难以接受的。因此我们希望减少遍历的次数,甚至一次遍历便可以解决问题,哪怕是遍历过程中需要牺牲空间来存储数据也是可以考虑的,因为我们擅长的是对数组的操作。

这时候我们考虑放弃不连续的 BFS 采用 DFS 进行遍历,因为 DFS 能够保证序列在树中的连续性,也方便多次利用。因此我们首先需要建立一个 visit[]数组存储整个遍历过程中访问到的点,这个数组究竟开多大呢?假设树中有 n 个节点。那么 visit[]数组的大小一定是 2n-1,为什么是 2n-1?请看下图:

这棵树的节点数为7, DFS 访问序列为 A B D B E B A C F C G C A ,共访问过 13 个节点。大家会告诉我,这棵树已经画到这里了,当然能数出来访问了几个点,如果没画出来只告诉节点总数,那怎么判断,还会这么准吗?模仿 DFS 过程,我们会发现正好每两个节点之间连接的线段(后面我将称之为 “枝干”)都被经过了两次,不论是在 DFS 深入的过程中还是回退的过程中,每个枝干总是顺着前进的方向向前对应一个目标节点,也就是说经历过多少次枝干,就访问过多少个节点,在一棵 n 个节点的树中,枝干的数量为 n - 1,因此,在一次 DFS 过程中,经过 2 *(n-1)次枝干,也就访问过 2*(n-1)个节点,但刚才我说过 访问的节点数是 2n-1,怎么还少了一个呢?仔细观察树的图就会发现,除了根节点,其余子节点都有一个由父节点指向自己的枝干,由于根节点无父节点,因此没有指向自己的枝干,在开始访问一棵树的时候没有枝干的引导也会默认访问到根节点,因此忽略掉的那次便是开始访问根节点的那一次,由此可以断定,visit[]数组一定是包含 2n-1个元素。

对树的遍历和存储已经完成,接下来如何实现在 visit[]数组中进行最近公共祖先的查找。我们在进行 DFS 遍历的过程中不难发现,在进行待求两个子节点之间的那部分节点遍历过程中,能够到达的层数最小的节点就是所求的最近公共祖先节点。直接上图可能更直观一些,如下丑图:

上图中所画出的路径是 DFS 遍历路径的一部分,路径上伸出的触角(小箭头)指向的是在相应位置的时候访问的节点。我们假设现在需要求解 T 和 J 两个节点的最近公共祖先 B ,由图可知蓝色路径部分即为 T 和 J 两节点之间的 DFS 路径,我们会发现 B 节点恰好就是处在整段蓝色路径上的所有节点中层数最小的一个。这个规律同样也适用其他任意两个节点。我们知道visit[]数组和 DFS 访问 的顺序是一致的,因此我们就可以从 visit[]数组的相应区间寻找层数最小的节点,这就要求我们对应 visit[]数组,开辟同样大的数组 level[]来存储每个节点对应的层数。还有一点需要确定就是相应的区间范围,为避免选的区间过长同时保证万无一失,我们从两个待求子节点首次出现的位置截取区间,因此需要将每个节点首次出现的序号也存储。

具体的代码实现部分如下:

由于 今天的主题是 LCA ,因此代码部分实现 RMQ 的过程被省略了,还没有看过这部分知识的朋友可以查看历史消息,了解 RMQ 问题的详细知识,(RMQ的链接在这里呦:RMQ问题第一弹)在此不做过多的赘述,图片不够清晰的朋友打开网页http://paste.ubuntu.com/25407878/查看网页版的代码。

编者的话

今天的分享到这里就结束了,还是老规矩,如果哪位在阅读文章的过程中发现写的不合适或者有错误的地方,欢迎在下方留言区进行纠正,万分感谢。

同时感谢一直关注我的公众号、关注我文章的朋友,在此奉上我的膝盖。

没有关注公众号的朋友可以识别下方二维码关注我的公众号查看最新的文章。


LCA问题第二弹的更多相关文章

  1. 浅谈Hybrid技术的设计与实现第二弹

    前言 浅谈Hybrid技术的设计与实现 浅谈Hybrid技术的设计与实现第二弹 浅谈Hybrid技术的设计与实现第三弹——落地篇 接上文:浅谈Hybrid技术的设计与实现(阅读本文前,建议阅读这个先) ...

  2. 前端学习 第二弹: JavaScript中的一些函数与对象(1)

    前端学习 第二弹: JavaScript中的一些函数与对象(1) 1.apply与call函数 每个函数都包含两个非继承而来的方法:apply()和call(). 他们的用途相同,都是在特定的作用域中 ...

  3. 青瓷引擎之纯JavaScript打造HTML5游戏第二弹——《跳跃的方块》Part 10(排行榜界面&界面管理)

    继上一次介绍了<神奇的六边形>的完整游戏开发流程后(可点击这里查看),这次将为大家介绍另外一款魔性游戏<跳跃的方块>的完整开发流程. (点击图片可进入游戏体验) 因内容太多,为 ...

  4. typecho流程原理和插件机制浅析(第二弹)

    typecho流程原理和插件机制浅析(第二弹) 兜兜 393 2014年04月02日 发布 推荐 1 推荐 收藏 14 收藏,3.7k 浏览 上一次说了 Typecho 大致的流程,今天简单说一下插件 ...

  5. 线段树+RMQ问题第二弹

    线段树+RMQ问题第二弹 上篇文章讲到了基于Sparse Table 解决 RMQ 问题,不知道大家还有没有印象,今天我们会从线段树的方法对 RMQ 问题再一次讨论. 正式介绍今天解决 RMQ 问题的 ...

  6. Hadoop基础-MapReduce的工作原理第二弹

    Hadoop基础-MapReduce的工作原理第二弹 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Split(切片)  1>.MapReduce处理的单位(切片) 想必 ...

  7. 『PyTorch』第二弹重置_Tensor对象

    『PyTorch』第二弹_张量 Tensor基础操作 简单的初始化 import torch as t Tensor基础操作 # 构建张量空间,不初始化 x = t.Tensor(5,3) x -2. ...

  8. Java基础-程序流程控制第二弹(循环结构)

    Java基础-程序流程控制第二弹(循环结构) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 流程控制有三种基本结构:顺序结构,选择结构和循环结构.一个脚本就是顺序结构执行的,选择结 ...

  9. 高并发第二弹:并发概念及内存模型(JMM)

    高并发第二弹:并发概念及内存模型(JMM) 感谢 : 深入Java内存模型 http://www.importnew.com/10589.html, cpu缓存一致性 https://www.cnbl ...

随机推荐

  1. php设计模式--注册器模式

    之前介绍过工厂模式和策略模式有两种方式去调用方法,一种是用工厂方法调用,一种是单例调取实例,那实际上这两种都有一个缺点(可能也不应该称为缺点,也可叫不足),有一个比较好的方法就是已经创建好的这个对象, ...

  2. 怎么检测JDK环境变量是否配置正确

    怎么检测JDK环境变量是否配置正确.. 点击开始--运行--输入cmd,点击确定. 在命令行窗口输入java  然后Enter.没有出现java既不是内部命令也不是外部命令.说明配置是正确的. 在命令 ...

  3. FBI Warning

    FBI Warning... --------------------------- 电影中的片头部分:<正宗的FBI Warning>: ========== 翻译: fbi warni ...

  4. 使用webpack热加载,开发多页面web应用

    我们一般使用webpack热加载开发SPA应用,但工作中难免会遇到一些多页面的demo或项目. 故参考 kingvid-chan 的代码,搭了一个使用HRM开发多页面web应用的脚手架,刚好也进一步学 ...

  5. Es6 类的关键 super、static、constructor、new.target

    ES6引入了Class(类)这个概念,作为对象的模板,通过class关键字,可以定义类.基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对 ...

  6. sphinx随笔记了一下

    sphinx笔记 一:下载中文版coreseek包1:解压后,将etc下的mysql.conf文件复制一份放到上级目录下,改名为sphinx.conf2:配置文件: 2.1:source配置数据源so ...

  7. 记录-新建一个web应用的过程与曲折

    第一步/ 打开eclipse,菜单栏下,File–New–Other-,打开后找到web–Dynamic Web Project,然后单击Next. 解释一下,Dynamic ,动态的,变化的,Dyn ...

  8. SQL优化工具

    SQL优化工具 什么是索引? 打个比方,我们在使用MySQL用作查询的时候就好比查字典,索引就好比字典的偏旁部首页.如果没有索引我们查询一个文字就需要一页页的翻,显然这种方式效率很低.如果我们对某一字 ...

  9. pythonl练习

    练习:用户输入姓名.年龄.工作.爱好 ,然后打印成以下格式 ------------ info of Egon ----------- Name : Egon Age : 22 Sex : male ...

  10. mac 终端常用目录跳转命令

    以前一直都是使用Windows系统,连命令行都没怎么用过.来到了Mac,在某位大神的诱导下,我开始尝试使用Mac Terminal,下面总结的是一些简单的目录跳转命令  (菜鸟级) .  文件目录 首 ...