什么是堆?
堆通常是一个可以被看做一棵完全二叉树的数组对象。——百度词条
堆又分为大根堆和小根堆
- 大根堆(最大堆),即每个父节点都大于等于他的子节点
- 小根堆(最小堆),即每个父节点都小于等于他的子节点 一般情况下,堆排序使用的是最大堆。
做堆排序之前的准备工作
如何将一个数组变成为一个最大堆
首先将数组和二叉树对应起来
先看一个例子:
[4 8 3 6 1 2]这个数组是下面这棵完全二叉树通过从上到下、从左到右的顺序遍历的结果,这样我们就可以看出数组的下标和完全二叉树之间的关系了。
这里的i表示第i个节点,parent(i)表示第i个节点的父节点,left(i)表示第i个节点的左孩子,right(i)表示第i个节点的右孩子。
以8为例,8的下标是1,即i=1,它的父节点为$(1-1)/2=0$,左孩子就是$1 \times 2 + 1 = 3$,右孩子为$1\times2+2=4$。确定好数组和二叉树的对应关系,接下来就是如何将二叉树变成一个最大堆。
生成最大堆
生成最大堆我们就需要将节点的值进行交换,但是交换的时候就要考虑是从下到上,还是从上到下交换,我一般喜欢从下往上,因为我没感觉比较符合一般递归的思路,就以下面这个为例:
从下往上我们应该从下面那个元素开始呢,我们一般都是从最后一个叶子节点的父节点开始的,因为最后一个叶子节点就是数组的最后一个元素,即这里的2,所以使用起来比较方便,又根据上面求父节点的公式可知,第一个进行比较的节点是下标为3的元素。这里下标为3的元素的值比其左右孩子的值大,所以不用交换。
那下一个进行比较的节点是哪个呢,因为叶子节点是没有左右孩子的,所以不考虑叶子节点,之后我们选择下标为2的节点进行比较(这里为什么选择2,下面会进行说明),下标为2的元素比其左右孩子都大,不用交换。
然后对下标为1的节点做同样操作,最后对根节点做比较。
下面说明一下上面选择下标为2进行比较的原因:
根据这张图可以看出,我们进行比较的顺序,既是从下往上,而且还具有规律,即进行比较的元素下标从第一个开始的下标逐渐减小到零,这样为我们编写代码提供了便利。
其实,这里还有一个问题,上面为了简化过程就直接拿一个已经是最大堆了,下面举个反例:
从图上可以看出,1的左子树已经是最大堆了,右子树也是,但是按照原来的规则修改之后,变成:
右子树不满足最大堆的定义了,这里我们还用进行一步比较,变成:
请大家记住这种情况,这里是我们之后写代码为什么需要递归的原因。
开始编写建最大堆的代码(java代码)
1 |
|
正式开始排序
我们继续举例:
这已经是最大堆了,根据最大堆的定义或者看上面这张图,我们可以知道,最大值一定是根节点那个元素。
关于上图再进行一些说明,为了便于理解,上面说将最后一个元素去掉,这里其实不是去掉,因为这里我们一直操作的使数组,最后一个元素其实是放到了数组的末尾,我们不再对末尾的元素进行操作了,所以这里说成去掉了。就拿步骤六的结果来说,二叉树从上到下,从左到右便利的结果是:[3 1 2],排好需的是[4 5 10],这里排好后的结果并不是 去掉了而是放到了数组的末尾,合起来就是[3 1 2 4 5 10]。
排序代码实现
1 |
|
不足之处,还请指出,创作不易,转载请注明出处