LCA问题第二弹
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问题第二弹的更多相关文章
- 浅谈Hybrid技术的设计与实现第二弹
前言 浅谈Hybrid技术的设计与实现 浅谈Hybrid技术的设计与实现第二弹 浅谈Hybrid技术的设计与实现第三弹——落地篇 接上文:浅谈Hybrid技术的设计与实现(阅读本文前,建议阅读这个先) ...
- 前端学习 第二弹: JavaScript中的一些函数与对象(1)
前端学习 第二弹: JavaScript中的一些函数与对象(1) 1.apply与call函数 每个函数都包含两个非继承而来的方法:apply()和call(). 他们的用途相同,都是在特定的作用域中 ...
- 青瓷引擎之纯JavaScript打造HTML5游戏第二弹——《跳跃的方块》Part 10(排行榜界面&界面管理)
继上一次介绍了<神奇的六边形>的完整游戏开发流程后(可点击这里查看),这次将为大家介绍另外一款魔性游戏<跳跃的方块>的完整开发流程. (点击图片可进入游戏体验) 因内容太多,为 ...
- typecho流程原理和插件机制浅析(第二弹)
typecho流程原理和插件机制浅析(第二弹) 兜兜 393 2014年04月02日 发布 推荐 1 推荐 收藏 14 收藏,3.7k 浏览 上一次说了 Typecho 大致的流程,今天简单说一下插件 ...
- 线段树+RMQ问题第二弹
线段树+RMQ问题第二弹 上篇文章讲到了基于Sparse Table 解决 RMQ 问题,不知道大家还有没有印象,今天我们会从线段树的方法对 RMQ 问题再一次讨论. 正式介绍今天解决 RMQ 问题的 ...
- Hadoop基础-MapReduce的工作原理第二弹
Hadoop基础-MapReduce的工作原理第二弹 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Split(切片) 1>.MapReduce处理的单位(切片) 想必 ...
- 『PyTorch』第二弹重置_Tensor对象
『PyTorch』第二弹_张量 Tensor基础操作 简单的初始化 import torch as t Tensor基础操作 # 构建张量空间,不初始化 x = t.Tensor(5,3) x -2. ...
- Java基础-程序流程控制第二弹(循环结构)
Java基础-程序流程控制第二弹(循环结构) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 流程控制有三种基本结构:顺序结构,选择结构和循环结构.一个脚本就是顺序结构执行的,选择结 ...
- 高并发第二弹:并发概念及内存模型(JMM)
高并发第二弹:并发概念及内存模型(JMM) 感谢 : 深入Java内存模型 http://www.importnew.com/10589.html, cpu缓存一致性 https://www.cnbl ...
随机推荐
- CSS3笔记之第四天
CSS3 2D 转换 了解2D变换方法: translate() rotate() scale() skew() matrix() translate()方法,根据左(X轴)和顶部(Y轴)位置给定的参 ...
- java-生产者消费者模式
经常会有公司叫我们手撕代码,比如网易,阿里,那我们是不是该掌握下呢.下面这段代码来自<现代操作系统>进程与线程P49页. public class ProducerConsumer { p ...
- [REST] 1.REST的起源
0. 世界上第一个网站 1990年12月20日,这一天对于现在的互联网来说意义非凡.欧洲核子研究组织(CREN)的科学家Tim Berners-Lee在一台NeXT电脑上启动了世界上的第一个网站(当然 ...
- HTML5_input_file_打开很慢的问题
最近项目中有上传附件的功能,只是在chrome浏览器上面测试,发现上传附件,打开选择框比较慢 原文链接:http://www.foreverpx.cn
- C#连接Firebird方法
Firebird Data Provider For .NET 连接 Firebird 数据库文件 下载 Firebird 嵌入式数据库:Firebird-2.5.0.25920-0_Win32_em ...
- python利用urllib实现的爬取京东网站商品图片的爬虫
本例程使用urlib实现的,基于python2.7版本,采用beautifulsoup进行网页分析,没有第三方库的应该安装上之后才能运行,我用的IDE是pycharm,闲话少说,直接上代码! # -* ...
- 优化关键渲染路径CRP
什么是关键渲染路径? 从收到 HTML.CSS 和 JavaScript 字节到对其进行必需的处理,从而将它们转变成渲染的像素这一过程中有一些中间步骤 浏览器渲染页面前需要先构建 DOM 和 CSSO ...
- GeoServer初识与安装
学习过程中发现官网上的东西足够基础了,所以在这只做一下索引和补充. 官方网址:http://live.osgeo.org/zh/overview/geoserver_overview.html 安装: ...
- C# 获取exe、dll中的图标,支持获取256x256分辨率
在网上找过许多文章,都没有成功获取过大图标,只能获取最大32x32.最后自己尝试了相关的windows api,终于找到一个可用的. 主要用到的C++的PrivateExtractIcons函数,具体 ...
- Swiper 滑动
1.http://www.swiper.com.cn/download/ 下载Swiper.JS Swiper.CSS 2.引入项目,添加html <div class="cont ...