1.介绍:
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种,它的最坏,最好,平均时间复杂度均为O(nlogn)。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。在数组的非降序排序中,需要使用的就是大根堆,因为根据大根堆的要求可知,最大的值一定在堆顶。
2.什么是堆:
堆具有以下性质:
- 它是完全二叉树:
- 每个结点的值都大于或等于(小于或者等于)其左右孩子结点的值。
所以对于堆中的父子节点之间对应关系为:
大顶堆:arr[ i ] >= arr[ 2 * i + 1] 并且 arr[ i ] >= arr[ 2 * i + 2] ,其中 0 <= i <= arr.length - 1
小顶堆:arr[ i ] <= arr[ 2 * i + 1] 并且 arr[ i ] <= arr[ 2 * i + 2] ,其中 0 <= i <= arr.length - 1
其中,最后一个非叶子节点的编号(下标)为:arr.length / 2 - 1
3.堆排序基本思想:
这里我们以大顶堆来举例,小顶堆的思想类似。一共分为3步:
1. 将待排序数组创建(调整)成一个大顶堆,此时,堆顶元素是数组的最大值。
2. 将堆顶元素与末尾元素进行交换,此时末尾元素为最大值。
3. 将剩余的元素[0.....n-1]重新调整成一个大顶堆,现在问题就转换为了对这n-1个元素进行堆排序了。反复执行以上步骤,直到全部元素都有序。
下面我们以大顶堆为例,来演示堆排序的过程:
初始状态:
创建堆:
我们的目的是使得堆顶为最大的元素。首先,叶子节点满足堆的性质,我们不需要调整。从最后一个非叶子节点开始,它的下标为:arr.length / 2 - 1,即上图中的下标为2,元素值为1的节点,我们令当前调整的元素为节点i,让当前的节点与左右孩子节点进行比较,取左右孩子节点的最大值。(1)如果孩子节点的最大值不比节点i大,那么就不交换,继续调整节点i-1;(2)如果孩子节点的最大值比当前节点要大,那么就交换。交换以后,被交换的孩子节点可能不满足堆的性质,所以我们需要继续对孩子节点进行同样的操作,直到节点i对应的分支全部满足堆结构。从节点i开始,每次i = i - 1 ,一直到堆顶节点。这样当所有的非叶子节点都调整为满足堆结构以后,堆顶元素就是最大的。
步骤1:
步骤2:
步骤3:
步骤4:
由于步骤3中12和8的交换导致孩子节点(节点值为8)分支不满足堆结构,所以需要调整孩子节点。
至此:大顶堆就创建好了,堆顶元素为最大的,接下来把堆顶元素和末尾元素进行交换。
首尾交换:
此时尾部元素12已经有序,并且为最大的,可以把它砍掉,我们继续对剩下的n-1个元素进行同样的操作就可以了。把1放在堆顶后,目前的结构不满足堆的性质,需要对堆顶元素按照上面的步骤进行调整,直到数组中的所有元素有序为止。
4.代码演示(Java版):
import java.util.Arrays;
public class MaxHeap {
public static void main(String[] args) {
int[] arr = new int[]{8,6,1,12,10,7};
sortHeap(arr);
System.out.println(Arrays.toString(arr));
}
/**
* 堆排序
* @param arr
*/
public static void sortHeap(int[] arr){
//构建最大堆
for(int i = arr.length / 2 - 1; i >= 0; i--){
adjustHeap(arr, i, arr.length);
}
//首位交换,并重新调整堆结构
for(int j = arr.length - 1; j >= 0; j--){
//交换
int temp = arr[j];
arr[j] = arr[0];
arr[0] = temp;
//继续调整
adjustHeap(arr, 0, j);
}
}
/**
* 调整最大堆
* @param arr 原数组
* @param i 当前需要调整的节点的编号
* @param length 剩余节点的个数
*/
public static void adjustHeap(int[] arr,int i,int length){
int temp = arr[i];//当前节点的值
for(int k = 2 * i + 1; k < length; k = 2 * k + 1){
//获取当前节点的孩子节点的最大值
if(k + 1 < length && arr[k + 1] > arr[k]){
k = k + 1;
}
//如果孩子节点更大,则把最大的孩子节点的值赋值给父节点
if(arr[k] > temp){
arr[i] = arr[k];
i = k;
}
else break; //说明当前节点往下都不需要调整了
}
arr[i] = temp;
}
}