持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情
引子
网上讲堆排序的挺多的,为什么还要赘述此篇,是因为每个人的关注点不一样,比如说堆排序对我来说,最大的难点,不是原理,而是每隔一段时间,再来回顾的时候,总是不能顺利写出代码,非要错个一两回才行。所以此篇根据自身的思考习惯,来理清一下需要掌握的知识点。
堆-这个数据结构
首先堆排序
里的堆
不是堆栈
的堆
。
之所以叫堆,可能是因为如果画出来这个数据结构的话,很像一个堆,就是一堆东西hu在一起的样子。想象成金字塔也是比较形象的。
这个数据结构包含多个元素,这些元素之间是有上下级的关系,实际上,就是一个树状结构,而且是二叉树。
但是堆这种二叉树,又有一些特殊性,比如说,这是一颗完全二叉树,什么叫完全二叉树呢?
我们把这颗二叉树画出来,层序遍历,从根到下,从左到右,一直遍历到最后一个元素,如果在这个过程中,没有缺失的节点,那么就叫完全二叉树,看文字不形象,看下图:
再来看看一个反例:
我们可以看见,染色的节点,缺失了子节点,所以最后一层遍历的时候,从左到右,不完整了,所以叫非完全二叉树
。
除了要是一颗完全二叉树
之外,还要满足一个特点才能称之为堆
。
那就是每一个节点的值,都必须小于或等于左右两个子节点的值。(反之亦可)。
我们来看一个例子:
观察上图,
- 首先它是完全二叉树;
- 其次,节点的值 子节点。
存储堆
虽然堆是一颗树,但是用数组也是可以来存储的。为什么?
这就是完全二叉树
的好处。
就拿上图中的例子来说,层序遍历,从根到下,从左到右,把这些节点依次放到一个数组里,就存好了。
这样是不会丢失上下级关系的,从下标而言,假设数组里某元素的下标是i
, 这个元素的上级节点的下标就是 。我们来画一下这个图:
反过来,下标是i
的节点的
- 左子节点的下标是
- 右子节点的下标是
当然,非要用树状结构来存储也是可以的, 并不需要非要用数组。
我们用数组来存,一般是为了搞堆排序。
堆排序之 heapify
现在有一个无序数组,肯定不是一颗合法的堆。
我们需要让这个数组变成一个合法的堆,这个操作叫 heapify,翻译叫 堆化。
down 操作
在 heapify 的过程中,又有一个小操作需要掌握,叫down操作。
必须条件:对于一个节点,如果左右节点所构成的树,都是合法的堆,而恰恰就是这个节点和左右两个子节点,不满足大小关系的时候,才可以对这个节点进行down操作。
我们来画一下这个必须条件:
我们发现,根节点不满足大小关系,但是根节点的左右两个子节点所构成的树,都满足堆的关系。
此时,对于根节点,才可以进行down操作,这个很重要。
down操作逻辑
我们根据上面的图来讲解,既然根节点不满足大小关系,很显然,我们需要将根节点往下降
- 也就是在左右两个子节点中,选取一个,与根节点交换
那么选取哪一个呢?选取更小的那个就行,如果两个子节点同样大小,那么就随便选取一个,例如上图,两个子节点的值都是1,那么就随便选。
我们画出交换之后的图:
本来左边是好的,交换之后,左边又不行了,不怕,继续往下降就行了!一直降到满足为止。
heapify 逻辑
上面的例子中,如果之后根节点不满足的话,只需要对根节点进行down操作即可。
但现实是,一个无序的数组,肯定不仅仅是根节点不满足,而是所有节点都不满足。
思路就是,尽量一点点的让整个结构满足堆的关系。
我们从最后一个有子节点的节点开始,进行down操作,一直往前遍历,一直遍历到根节点,此时数组就完成了堆化。
这里之所以可行是因为,我们每次进行down操作的时候,确实已经满足了down操作的那个必须条件
。
堆排序具体逻辑
在完成heapify之后,就可以真正来进行堆排序了。
我们假设heapify的时候,采用的大小关系是,谁大,谁就往上升。
那么一个heapify之后的数组的第一个元素就是最大的。
此时,我们把第一个元素和最后一个元素互换。
我们会得到两个结果:
- 最大的元素已经排到了最后,这就是排序所需要的
- 忽略最后一个元素,前面的所有元素构成的是一个不那么完美的堆。因为根节点有可能不满足大小关系
此时我们只需要对根节点进行down操作,这次操作的时候,一定要忽略最后一个元素。
然后,就又得到一个好堆了。
画一个图就明白了:
图中白色部分是一个好堆。
在进行两个操作之后,数组的最后一个元素已经是最大的了,而且数组前面部分又是一个好堆。
我们一直进行这两个操作,最后得到的就是一个排好的数组,从小到大的顺序。
要反向排序的话,上面heapify的时候,只需要切换成小的在上面即可。
重要的操作总结
- heapify 将整个树变成一个合法的堆,具体过程就是从尾部往前遍历每个节点,进行down操作即可。(优化成,从尾部第一个拥有子节点的元素开始)
- down 如果一个节点和两个子节点不满足大小关系,那么就将这个节点往下降,一直往下降,降到满足即可。