经典排序之堆排序详解
堆排序
一、概述
首先我们来看看什么叫做堆排序?
若在输出堆顶的最小值之后,使得剩余的n-1个元素的序列重新又构成一个堆,则得到n个元素中的次小值,如此反复,便能得到一个有序序列,称这个过程为堆排序。
再来看看总结一下基本思想:
- 将无序序列建成一个堆
- 输出堆顶的最小(大)值
- 使剩余的n-1个元素又调整成一个堆,则可得到n个元素的次小值
- 重复执行,得到一个有序序列
通过上面的规律发现两个问题,而堆排序需要解决这两个问题:1.如何建堆? 2.如何调整?
二、如何建堆
1.什么是堆?
n个元素的序列{k1,k2,…,kn},当且仅当满足下列关系时,成为堆:
如果将序列看成一个完全二叉树,非终端结点的值均小于或大于左右子结点的值。
利用树的结构特征来描述堆,所以树只是作为堆的描述工具,堆实际是存放在线形空间中的。
解释: 从上面可以知道,当父节点大于左右孩子节点 或者父节点小于节点的时候,可以称为堆,前者称为大顶堆,后者称为小顶堆。
2.建堆
从第n/2 向下取整 个元素起,至第一个元素止,进行反复筛选,如果我们要建立大顶堆,先比较左右孩子的大小,将比较大的孩子和父节点进行比较。
因为数组的元素一共是7个元素,我们应该从第3个元素,首先我们应该比较第3个元素的左右孩子的大小,然后再进行调整。
代码如下:
// 建立大顶堆
public void buildHeap(int[] arr){
for (int i = arr.length / 2 - 1; i >= 0; i--) {
//从第一个非叶子结点从下至上,从右至左调整结构
adjustHeap(arr, i, arr.length);
}
}
/**
* 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
*
* @param arr
* @param i
* @param length
*/
public static void adjustHeap(int[] arr, int i, int length) {
int temp = arr[i];//先取出当前元素i
for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {//从i结点的左子结点开始,也就是2i+1处开始
if (k + 1 < length && arr[k] < arr[k + 1]) {//如果左子结点小于右子结点,k指向右子结点
k++;
}
if (arr[k] > temp) {//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
arr[i] = arr[k];
i = k;
} else {
break;
}
}
arr[i] = temp;//将temp值放到最终的位置
}
/**
* 交换元素
*
* @param arr
* @param a
* @param b
*/
public static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
三、如何调整
当我们将数组进行建立大顶堆或者小顶堆的时候,我们就会发现堆顶元素必然是该排序数组的最大或者是最小的那个数据。如何在输出堆顶元素后调整,使之成为新堆?
- 输出堆顶元素后,以堆中最后一个元素替代之
- 将根结点与左、右子树根结点比较,并与小者交换
- 重复直至叶子结点,得到新的堆
解释:
这时候我们将堆顶元素和最后一个元素进行交换,那么我们调整前n-1个元素成堆,反复操作即可
四、算法分析
时间效率:$ O(nlog2n) $
空间效率:$ O(1)$
稳 定 性:不稳定
适用于n 较大的情况
五、完整代码
public class HeapSort {
public static void main(String[] args) {
int[] arr = {9, 8, 7, 6, 5, 4, 3, 2, 1};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static void sort(int[] arr) {
//1.构建大顶堆
for (int i = arr.length / 2 - 1; i >= 0; i--) {
//从第一个非叶子结点从下至上,从右至左调整结构
adjustHeap(arr, i, arr.length);
}
//2.调整堆结构+交换堆顶元素与末尾元素
for (int j = arr.length - 1; j > 0; j--) {
swap(arr, 0, j);//将堆顶元素与末尾元素进行交换
adjustHeap(arr, 0, j);//重新对堆进行调整
}
}
/**
* 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
*
* @param arr
* @param i
* @param length
*/
public static void adjustHeap(int[] arr, int i, int length) {
int temp = arr[i];//先取出当前元素i
for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {//从i结点的左子结点开始,也就是2i+1处开始
if (k + 1 < length && arr[k] < arr[k + 1]) {//如果左子结点小于右子结点,k指向右子结点
k++;
}
if (arr[k] > temp) {//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
arr[i] = arr[k];
i = k;
} else {
break;
}
}
arr[i] = temp;//将temp值放到最终的位置
}
/**
* 交换元素
*
* @param arr
* @param a
* @param b
*/
public static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}
欢迎关注个人公众号:Coder辰砂