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. sweetalert------一个非常萌的alert!

    今天逛github的时候发现一个非常萌的alert,比IE和各大浏览器的原alert美多惹. github项目地址:https://github.com/t4t5/sweetalert 通过git c ...

  2. expander graph&random walk的一个小应用

    此文主要总结的是一种随机算法,旨在判断一个expander图上两点是否连通.复杂度O(logn).算法思路清奇. expander graph博大精深,如果对expander graph的生成,fam ...

  3. golang channel无缓冲通道会发生阻塞的验证

    公司搞了午间技术par,本周我讲的主题是关于无缓冲通道channel是否会发生阻塞,并进行了验证. go语言中channel分为无缓冲通道和有缓冲通道两种 channel提供了一种在goroutine ...

  4. Unix硬链接和符号链接(转)

    首先要弄清楚,在Linux系统中,内核为每一个新创建的文件分配一个Inode(索引结点),每个文件都有一个惟一的inode号.文件属性保存在索引结点里,在访问文件时,索引结点被复制到内存在,从而实现文 ...

  5. LNMP1.4环境中安装fileinfo插件

    安装前: 安装前建议先执行 /usr/local/php/bin/php -m (此命令显示目前已经安装好的PHP模块)看一下,要安装的模块是否已安装. 然后下载当前PHP版本的源码并解压. 安装: ...

  6. centos 7 最小安装后 ip配置

    安装玩CentOS7 后要进行 ip的配置 vi /etc/sysconfig/network-scripts/ifcfg-eth0 在里面输入 NAME=eth0 HWADDR=XX:XX:XX:X ...

  7. jmeter性能测试 套路二

    1.一般我们不会通过下面这种去跑性能测试 2.我们会通过这种方式去跑性能测试 3.录制自动化 就用新的 4.录制性能测试  就用

  8. robot framework 牛刀一试

    1.New Project   Type选择Directory,Format选择TXT 2.New Suite   在Project的基础上Create New Suite,Type选择File,Fo ...

  9. [C]字符串排序之-冒泡法

    在oj刷题,遇见一题字符串排序题. 脑海里瞬间闪过数组排序. 思路有了,打开题解看看别人的思路,发现好多人的排序方法显得比较臃肿,可能也是我的水平不够,欣赏不来吧. 不过用冒泡法排序的时候一定要记得字 ...

  10. Linux的netstat查看端口是否开放见解(0.0.0.0与127.0.0.1的区别)

    linux运维都需要对端口开放查看  netstat 就是对端口信息的查看 # netstat -nltp p 查看端口挂的程序 [root@iz2ze5is23zeo1ipvn65aiz ~]# n ...