0
点赞
收藏
分享

微信扫一扫

数据结构 ---> 二叉树 -->堆之解析_01

老友们好 !本期将对堆之构建进行解说 !

相信,初学此章节的老友 !!或多或少,对前一期的部分代码,有所困惑!

说真的,堆的有些内容理解起来还是挺困难的!

好了,废话不多讲!开始本期的解析之旅吧 !希望大家都能有所收获 !!

下面,先看一组,上一期的图示:>

数据结构 ---> 二叉树 -->堆之解析_01_向上调整算法

那么该如何操作,才能取得前几名的数值呢?

针对这点,部分老友,可能,会用数组的覆盖原理 !

即是 将堆顶元素弹出后,再找出次大的数值。这一后续过程,想到了值的覆盖原理 !那么,究竟是否正确?弊端在哪里?

下列图示,可为老友们,解答疑惑 !

数据结构 ---> 二叉树 -->堆之解析_01_完美特性_02

由上图可知,按照上述想到的方法,显然会出现严重的 “紊乱”现象发生!即是 父子兄弟关系,全部乱套了 !!

这样子,不仅效率低下,而且再找次大的时候,需要对剩余元素重新进行建堆 !而每一次建堆的时间复杂度是 O(N*logN)

有关于,建堆之时间复杂度,会在后续章节进行讲解 !!

而对上述问题,解决之道是封装一个函数,进行每次查找的时候

时间复杂度是 O(N) 这意味着,只需要每一次查找,遍历一遍即可,根本无须一遍又一遍的建堆操作 !!

然而这块思想逻辑的实现,是本章节的一大难点之一 !不好理解 !

下面图示,就是上一期所写就的 向上调整算法 !!

称之为算法,在这里一点儿都不过分 !!

数据结构 ---> 二叉树 -->堆之解析_01_向上调整算法_03

请注意,上述红色方框内的 条件判断 !!

而很多老友,不明白这种思想逻辑;

甚至对前面的二叉树遍历,并没有打牢基础,会很容易掉进坑里 !!

下面展现错误写法 :>

数据结构 ---> 二叉树 -->堆之解析_01_边界值问题_04

这种写法,逻辑上的漏洞是在那里?先说答案好了!

----> "parent" 与 “child” 会最终重合在一起,无法进行建大堆操作

数据结构 ---> 二叉树 -->堆之解析_01_完美特性_05

如上图所示,是小堆,然后建立大堆

若还是比较难理解,只需,将parent取特殊值检验即可

比如,令 parent = 0 ;进入循环体之后,满足条件,“孩子结点”更新为 下标是 0;此后“双亲结点”也会被更新为 下标是 0,注意取整操作,虽然 双亲结点---> parent = (0 - 1)/ 2 ---> - 1 / 2 , 取整后仍然是 0 ,此时便不符合建立大堆要求 !

现在,剖析一下,错误想法的来源,其实这才是有意思的地方 !😊

在函数形参部分,有一个双亲结点的传入,而双亲结点最开始遍历的时候,处于下标为 0 的位置。

因此,想当然认为,限制条件便是 “parent >= 0”   哈,这种错误想法是不是经过分析很肤浅呢?当时,初次写这个程序的时候,逻辑判断判断上的肤浅,差一点将我代入了坑里

不过后来,随着程序的展开,我进行了特殊值的代入,发现程序运行不起来,于是又进行了一次彻底思考,每一行代码每一行代码之间的逻辑进行分析,也没有调试!!最终确定了是“大前提”的问题。后来又改写,重新测试了两三遍,一举成功 !!😊😊

好了,各位老友 !!刚刚提起这件错误,并不是再讲解废话 !真正想要表达的是遇见错误,最好是遵循逻辑思考去定位错误,而不是盲目寻找 !!当然,要稳,别急躁,总会恰当时候找到的 !而那就是我们要找寻的答案 !

现在,继续推进,本章第二大难解之点 !

-----> 向下调整,建立大堆区

数据结构 ---> 二叉树 -->堆之解析_01_完美特性_06


针对以上问题,红色方框内的判断,是此次分析的重中之重 !!

-------> 红色方框内,若没有写 “child + 1 < n” 或者 条件次序颠倒,均会出现越界访问的现象发生 !!

下面,展开分析具体原因 : >

数据结构 ---> 二叉树 -->堆之解析_01_向上调整算法_07

下标为 7 之后的空间,均属于定长数组界域之外的部分,随着向下调整的不断进行

如果除了,最后一个叶子结点之外,还没有进行最后一次比较调整,此时,由于边界值的限定确实,不明确,会出现越界访问下一个空间,即下标为 7 的位置,此时 下标为 7 的位置“a[child + 1]”,如果此时下标为 7 的位置,产生的随机数小于下标为 6 的位置(最后一个叶子结点),那么正好停止调整,最终结果正确,并正确建立好了大堆 !!然而此种情况,几乎不存在,因为计算机自动产生的随机数,具有的随机性太大,不可能正好产生小于最后一个叶结点的数值,此时几率趋近于 零 !!

针对上述情况,还存在另一种条件次序颠倒的情况,此时,仍然导致越界访问的现象发生!

具体分析,很简单 !!逻辑上,并不存在新的点 !!同上述分析,一模一样,只是最后再加上 “child + 1 < n”, 几乎没有任何影响 !仍然是错误的结果 !!

以上两大难点,主要在于边界值,将边界值考虑好了,就可以完美规避好一些坑了!

另外,还需要说明两点,也是很重要的 !!

对于向下调整的前提是,对于大堆而言,必须是左右子树都是大堆;

那么,回归到了,一开始讲解堆之构建。什么时候,用到了向下调整算法 ?!

显然是,对 堆区中的元素进行删除的时候,用到了向下调整算法,至于具体怎么实现的,请看下列图示 :>

数据结构 ---> 二叉树 -->堆之解析_01_堆之构建解析_08

先是 交换数据,即 堆顶元素与叶子结点进行交换,然后就是进行向下调整,具体是否要进行调整,需要看双亲结点与子节点之间的大小。

若双亲结点均大于左右子树的子节点,则不会发生交换;若双亲结点小于其中一棵树中最大的那个子节点,那么同那个较大的子节点进行交换 !

请看下面图示 :>

数据结构 ---> 二叉树 -->堆之解析_01_向上调整算法_09

以上是向下调整算法,注意是满足“双亲结点小于两棵树中的那个较大的孩子结点”

这里一定要同孩子结点中那个较大的结点进行交换 !若同较小的进行交换,不符合大堆要求 !!

以上的操作过程,一开始就是以大堆树进行的,一定别忽视了 !!

此外,还有一点,需要解释一下 !为什么 堆删数据,删除的是堆顶元素,而不是堆区末尾元素 ?!

其实,可以这样理解,写程序的目的,同现实世界相互联系。既然堆顶元素要么是最大的元素,要么是最小的元素!

在进行堆中元素删除操作的时候,才会显得更有意义 !注意,叶子结点最后一个值,不一定就是此堆区(完全二叉树)中最小的或者最大的那一个元素 !

下面请看,堆删元素的递归示意图 :>

数据结构 ---> 二叉树 -->堆之解析_01_完美特性_10

对于向上调整的前提是,除了 Child 之外,前面都是堆(大堆 / 小堆)。

上一期,进行堆插元素的过程,用到了向上调整算法 !请看下列图示 :>

数据结构 ---> 二叉树 -->堆之解析_01_堆之构建解析_11

而尾插元素的代码流程如下 :> 

数据结构 ---> 二叉树 -->堆之解析_01_边界值问题_12

请注意看,上述红色方框,便是针对 堆入元素的向上调整算法!

其实,经过堆入元素的过程,最终建立的,就是想要的 大堆 或者 小堆 !

那么,对于建堆过程,该如何抉择是选大堆好呢?还是选小堆好呢?在这里,有个完美特性 :

数据结构 ---> 二叉树 -->堆之解析_01_边界值问题_13

那么,接下来,就对以上特性进行检验,是否属实 !

如果,升序, 建立小堆;则有以下图示:>

数据结构 ---> 二叉树 -->堆之解析_01_完美特性_14

由上图,还可以得出,经过每一次寻找最小值之后,重新建立的堆区,会使得原有关系全部乱套了 !

然而只需一次遍历就足够了,没必要用时间复杂度为 O(N * logN)的特性 !!

至于代码修改部分,建立小堆区的彩色代码如下,只需要修改三处判断大小的关系即可 !

数据结构 ---> 二叉树 -->堆之解析_01_向下调整算法_15

至于修改之后的运行结果 :>

数据结构 ---> 二叉树 -->堆之解析_01_向下调整算法_16

以上就是 排升序建立小堆区的证明,其实可以看出,弊端也是蛮大的 !

因此,还是 排升序 还是 建立大堆 !!不光效率高,而且时间复杂度还小 !!

鉴于本期信息量比较大,篇幅较长,并富有一定的挑战性 !!

希望各位好友,能对本期内容有所收获 !!

下一期继续推进,另一项证明与推导 !!敬请期待 !!😊😊


举报

相关推荐

0 条评论