堆排序详解
一、堆排序相关简介
堆:符合以下两个条件之一的完全二叉树:
- 根节点的值 ≥ 子节点的值,这样的堆被称之为最大堆,或大顶堆 。
- 根节点的值 ≤ 子节点的值,这样的堆被称之为最小堆,或小顶堆 。
堆排序过程如下:
- 用数列构建出一个大顶堆,取出堆顶的数字;
- 调整剩余的数字,构建出新的大顶堆,再次取出堆顶的数字;
- 循环往复,完成整个排序。
整体的思路就是这么简单,我们需要解决的问题有两个:
- 如何用数列构建出一个大顶堆;
- 取出堆顶的数字后,如何将剩余的数字调整成新的大顶堆。
构建大顶堆有两种方式:
- 从 0 开始,将每个数字依次插入堆中,一边插入,一边调整堆的结构,使其满足大顶堆的要求;
- 将整个数列的初始状态视作一棵完全二叉树,自底向上调整树的结构,使其满足大顶堆的要求。
在介绍堆排序具体实现之前,我们先要了解完全二叉树的几个性质。将根节点的下标视为 0,则完全二叉树有如下性质:
- 对于完全二叉树中的第 i 个数,它的左子节点下标:left = 2i + 1
- 对于完全二叉树中的第 i 个数,它的右子节点下标:right = left + 1
- 对于有 n 个元素的完全二叉树(n≥2)(n≥2),它的最后一个非叶子结点的下标:n/2 - 1
二、堆排序的代码实现
public class Heapsort {
public static void main(String[] args){
int[] arr = {2,3,5,1,2,4,5,6,8,6,7,9};
heapSort1(arr);
for (int i : arr) {
System.out.print(i+"-");
}
}
public static void heapSort1(int[] arr){
buildMaxHeap(arr);
for (int i = arr.length-1; i > 0 ;i --){
swap(arr,0,i);
maxHeapify(arr,0,i);
}
}
/**
* 初始化大顶堆
*/
public static void buildMaxHeap(int[] arr){
//从最后一个非叶子节点开始,向上调整
for (int i = arr.length/2 -1 ; i >= 0 ; i--){
maxHeapify(arr,i,arr.length);
}
}
public static void maxHeapify(int[] arr, int i, int heapSize){
//左子节点的下标
int left = 2 * i + 1;
//右子节点的下标
int right = 2 * i + 2;
//记录根节点,左子节点,右子节点三者中最大值小标
int largest = i;
if (left < heapSize && arr[left] > arr[largest]){
largest = left;
}
if (right < heapSize && arr[right] > arr[largest]){
largest = right;
}
if (i != largest){
swap(arr,i,largest);
maxHeapify(arr,largest,heapSize);
}
}
public static void swap(int[] arr, int i ,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
三、堆排序时间复杂度 & 空间复杂度
堆排序分为两个阶段:初始化建堆(buildMaxHeap)和重建堆(maxHeapify,直译为大顶堆化)。所以时间复杂度要从这两个方面分析。
根据数学运算可以推导出初始化建堆的时间复杂度为 O(n),重建堆的时间复杂度为 O(n\log n),所以堆排序总的时间复杂度为 O(n\log n)。推导过程较为复杂,故不再给出证明过程。
堆排序的空间复杂度为 O(1),只需要常数级的临时变量。
参考文章地址:https://leetcode-cn.com/leetbook/read/sort-algorithms/eu7ux3/