堆排序定义
一般来说,算法就像数学公式,前人经过不断优化和验证得到有规律性的公式留给后人使用,当然也会交给后人验证的思路。那么堆排序算法就是这样,它有基本固定的定义如下:
1、将数组构建为一颗有规则的完全二叉树
2、该二叉树任意父结点值必须大于(最大堆)或小于(最小堆)孩子结点
3、该二叉树除了最底层外,其它层都是从左往右充满地
4、该二叉树任意父结点左孩子数组下标 = 父结点数组下标 * 2
5、该二叉树任意父结点右孩子数组下标 = 父结点数组下标 * 2 + 1
6、该二叉树任意孩子父结点数组下标 = 孩子结点数组下标 / 2
7、该二叉树高度(所有父结点)= 数组长度 / 2
8、最后再对构建好的二叉树进行排序
图解堆排序
![2ad12c1fe123a6e94bf0f32880c2bd4f.png](https://img-blog.csdnimg.cn/img_convert/2ad12c1fe123a6e94bf0f32880c2bd4f.png)
下面我们通过堆排序算法来实现。
构建最大堆二叉树
将数组构建为一颗有规则的完全二叉树,该二叉树遵循任意父结点必须大于(最大堆)孩子结点。当然,构建最小堆也是可以的,具体看场景来设计,一般从小到大排序可以选择最大堆,从大到小排序可以选择最小堆。当然最大堆也可以实现从大到小排序。
最大堆二叉树定义如下:
1、左孩子结点数组索引=父结点数组索引 * 2。伪代码如下:
private int leftIndex(int i) { return 2 * i;}
2、右孩子结点数组索引=父结点数组索引 * 2 + 1。伪代码如下:
private int rightIndex(int i) { return 2 * i + 1;}
3、父结点数组索引=孩子结点数组索引 / 2
private int parentIndex(int i) { return i / 2;}
为了描述直观,数组下标从1开始,但实际编写代码在操作数组取值时候需要减去1,因为实际程序中的索引是从0开始的。
![808f185da2bb16b7fadc502bfa09915a.png](https://img-blog.csdnimg.cn/img_convert/808f185da2bb16b7fadc502bfa09915a.png)
假设我们按数组顺序开始构建
![b0c47275d6b02cb48ace53d2bc1bf18a.png](https://img-blog.csdnimg.cn/img_convert/b0c47275d6b02cb48ace53d2bc1bf18a.png)
上图满足了父子结点直接的数组索引计算定义,但是它不满足最大堆的定义(任意父结点必须大于孩子结点),父结点6比右子结点9要小,所以我们需要对原数组值进行交换(注意:只是将两个下标对应的值进行交换,下标不变)来满足最大堆二叉树的定义,调整后结果如下:
![e477211a7b454815ed38bfbbded2b617.png](https://img-blog.csdnimg.cn/img_convert/e477211a7b454815ed38bfbbded2b617.png)
此时,再根据定义绘制最大堆二叉树如下:
![7d696e34f6372e22e4b7444cbaae3b4d.png](https://img-blog.csdnimg.cn/img_convert/7d696e34f6372e22e4b7444cbaae3b4d.png)
下面我们继续按这种思路构建左孩子结点数组值为3下标为2的左右孩子结点,构建结果如下:
![8f95f546e7c07d55c671eb61b54fd7f8.png](https://img-blog.csdnimg.cn/img_convert/8f95f546e7c07d55c671eb61b54fd7f8.png)
按照下标计算公式构建如下:
![d7dc2cff9636e93bbd4343d3c08c3d39.png](https://img-blog.csdnimg.cn/img_convert/d7dc2cff9636e93bbd4343d3c08c3d39.png)
按照最大堆定义中任意父结点必须大于孩子结点交换后如下: