堆排序的基础是完全二叉树的顺序结构实现,没有实现过的可以先看一下这篇文章:
《二叉树链式结构和顺序结构区别详解+java代码实现》
https://blog.csdn.net/weixin_44537194/article/details/87405475
堆分为大顶堆和小顶堆,其实就是完全二叉树。
大顶堆要求对于任意一个子树,父节点大于任何一个子节点。
就像一个倒三角一样:
小顶堆相反,父节点小于任意个字节点,两者对左右孩子的大小关系不做任何要求,其实很好理解。
有了上面的定义,我们可以得知,处于最大堆的根节点的元素一定是这个堆中的最大值。
其实我们的堆排序算法就是抓住了堆的这一特点,每次都取堆顶的元素,将其放在序列最后面,然后将剩余的元素重新调整为最大堆,依次类推,最终得到排序的序列。
那么使用堆排序只需要两步:
1.把数组转化为大顶堆
2.把大顶堆元素固定在序列最后,剩余数字继续调整为大顶堆。
第一步:如何把数组调整为大顶堆?
先使用完全二叉树的顺序存储,把如图的数组转化为一个堆(二叉树)
从最后一个叶子节点(最后一层)向上处理,如果该子节点比他的父节点小,二者互换位置,但要注意移动后会打乱原来的排序,
也就是说需要再次比较新的节点是否比他之下的子节点小,如果小的话还需移动
最终完成大顶堆:
我们先通过代码实现这一步:
/**
* 2019年2月16日
*/
package demo6;
import java.util.Arrays;
public class HeapSort {
public static void main(String[] args) {
int[] arr =new int[]{9,6,8,7,0,1,10,4,2};
//堆调整为大顶堆的方式
//开始的位置是最后一个叶子节点的父节点
int start = (arr.length-1)/2;
//通过循环才可以全部查看调整
for(int i =start;i>=0;i--){
maxHeap(arr,arr.length,i);
}
System.out.println(Arrays.toString(arr));
}
/**
* 调整为大顶堆的方法
* arr为调整的堆
* length为长度
* parent为当前父节点
*/
public static void maxHeap(int[] arr, int length, int parent) {
// 取出它的左右子节点,根据顺序存储的二叉树
int leftNode = 2 * parent + 1;
int rightNode = 2 * parent + 2;
int max = parent;
// 找出最大
if (leftNode < length && arr[leftNode] > arr[max]) {
max = leftNode;
}
if (rightNode < length && arr[rightNode] > arr[max]) {
max = rightNode;
}
// 交换变量
int temp;
// 如果最大值变化,代表需要移动
if (max != parent) {
temp = arr[max];
arr[max] = arr[parent];
arr[parent] = temp;
// 交换之后,可能会破坏原来的顺序,需要继续向下查看
maxHeap(arr, length, max);
}
}
}
运行结果为:
与我们手动排序是一致的,验证通过。
接下来解决第二步: 把堆顶最大的元素固定到末尾,然后使用递归把剩余元素再次排成大顶堆
// 堆排序
public static void heapSort(int[] arr) {
// 第一步:堆调整为大顶堆的方式
// 开始的位置是最后一个叶子节点的父节点
int start = (arr.length - 1) / 2;
// 通过循环才可以全部查看调整
for (int i = start; i >= 0; i--) {
maxHeap(arr, arr.length, i);
}
System.out.println("调整为大顶堆:" + Arrays.toString(arr));
// 第二步:把堆顶固定到末尾,递归
for (int i = arr.length - 1; i > 0; i--) {
// 交换,固定最大数
int x = arr[0];
arr[0] = arr[i];
arr[i] = x;
// 递归,把剩余重新排序
// 父节点从零开始
maxHeap(arr, i, 0);
}
}
这里有一个血泪史:在第二步递归的时候,我们调用 “ 调整大顶堆的 ”方法,但是我把参数顺序写错了,一直得不到正确结果,强调一下
第一个参数是待调整数组,
第二个是调整的长度,因为数组尾部已调整好的元素冗余,长度不断减小
第三个时开始的父节点索引
排序结果及全部代码如下:
package demo6;
import java.util.Arrays;
/**
* 堆排序
*
* 2019年2月16日
*/
public class HeapSort {
public static void main(String[] args) {
int[] arr = new int[] { 9, 6, 8, 7, 0, 1, 10, 4, 2 };
heapSort(arr);
System.out.println("堆排序结果: " + Arrays.toString(arr));
}
// 堆排序
public static void heapSort(int[] arr) {
// 第一步:堆调整为大顶堆的方式
// 开始的位置是最后一个叶子节点的父节点
int start = (arr.length - 1) / 2;
// 通过循环才可以全部查看调整
for (int i = start; i >= 0; i--) {
maxHeap(arr, arr.length, i);
}
System.out.println("调整为大顶堆:" + Arrays.toString(arr));
// 第二步:把堆顶固定到末尾,递归
for (int i = arr.length - 1; i > 0; i--) {
// 交换,固定最大数
int x = arr[0];
arr[0] = arr[i];
arr[i] = x;
// 递归,把剩余重新排序
// 父节点从零开始
maxHeap(arr, i, 0);
}
}
/**
* 调整为大顶堆的方法
* arr为调整的堆
* length为长度
* parent为当前父节点
*/
public static void maxHeap(int[] arr, int length, int parent) {
// 取出它的左右子节点,根据顺序存储的二叉树
int leftNode = 2 * parent + 1;
int rightNode = 2 * parent + 2;
int max = parent;
// 找出最大
if (leftNode < length && arr[leftNode] > arr[max]) {
max = leftNode;
}
if (rightNode < length && arr[rightNode] > arr[max]) {
max = rightNode;
}
// 交换变量
int temp;
// 如果最大值变化,代表需要移动
if (max != parent) {
temp = arr[max];
arr[max] = arr[parent];
arr[parent] = temp;
// 交换之后,可能会破坏原来的顺序,需要继续向下查看
maxHeap(arr, length, max);
}
}
}