堆排序——Java代码实现及算法解析
一、前言
1. 什么是堆?
堆是一种重要的数据结构,它是一种完全二叉树。堆分为最大堆和最小堆,最大堆任意子树根节点不小于任意子结点,即每一个父节点一定大于其两个左右子节点;最小堆则与之相反,最小堆的根节点不大于任意子结点。底层如果用数组存储数据的话,假设某个元素为序号为 i (Java数组从0开始,i为0到n-1),如果它有左子树,那么左子树的位置是2i+1,如果有右子树,右子树的位置是2i+2,如果有父节点,父节点的位置是(n-1)/2 向下取整。
2. 堆排序的概念
所谓堆排序就是利用堆这种数据结构的性质来对数组进行排序,在数组的非降序排序中,需要使用的就是最大堆,因为根据最大堆的性质可知,最大的值一定在堆顶。堆排序一种不稳定的排序算法,其时间复杂度为O(nlogn)。
堆排序其实也是一种选择排序,是一种树形选择排序。只不过直接选择排序中,为了从 R[1···n] 中选择最大记录,需比较 n-1 次,因此 n 次交换时间复杂度为O(n)。以大顶堆升序排序为例,每次将堆顶值沉入到底部并且重新构建大顶堆的时间复杂度为O(log2n)。因此堆排序的整体时间复杂度为O(nlogn)。
二、堆排序的Java代码实现
1. 算法思想
- (1)构建最大堆;
- (2)选择顶,并与第0位置元素交换,堆长度减 1;
- (3)由于步骤(2)的的交换可能破环了最大堆的性质,即第0位置的元素不再是最大元素,则需要重新调整堆maxHeap(沉降法,下面会针对代码部分详细讲解),根据实际情况重复步骤(2),直至堆列表长度为 0 ,即升序排序完成。
堆排序中最重要的算法就是maxHeap,该函数假设一个元素的两个子节点都满足最大堆的性质(即左、右子树都是最大堆),只有根元素可能违反最大堆性质,那么把该元素以及左右子节点的最大元素找出来,如果该元素已经最大,那么整棵树都是最大堆,程序退出,否则交换根元素与最大元素的位置,继续调用maxHeap构建最大元素所在的子树。
2. Java代码解析
我们根据排序思路,逐步解析代码过程(完整代码在后面)。首先我们要根据输入的初始化序列,使得我们的堆结构成为最大堆。代码从堆最后一个非叶子结点( i = arr.length/2-1 )开始从下至上,从右至左调整堆结构,调整结构方法即为maxHeap方法。例如下图中堆,即从 index 为 9 / 2 - 1 = 3 的位置开始调整以 index = 3为顶的子树,将最大值升到顶部。
部分代码(构建大顶堆):
//1.构建大顶堆
for(int i = arr.length/2-1; i >= 0; i--){
//从堆最后一个非叶子结点从下至上,从右至左调整结构
maxHeap(arr, i, arr.length);
}
从 i = arr.length/2-1 节点开始,调用maxHeap方法,升降堆中的值调整堆结构。
public static void maxHeap(int[] arr, int i, int length){
int temp = arr[i];
for(int k = i*2 +1; k < length; k = k*