基本思想
堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
完全二叉树
上图就是一个完全二叉树,其特点在于:
1.从作为第一层的根开始,除了最后一层之外,第N层的元素个数都必须是2的N次方;第一层2个元素,第二层4个,第三层8个,以此类推。
2.而最后一行的元素,都要紧贴在左边,换句话说,每一行的元素都从最左边开始安放,两个元素之间不能有空闲,具备了这两个特点的树,就是一棵完全二叉树。
小顶堆:我们假设有一棵完全二叉树,在满足作为完全二叉树的基础上,对于任意一个拥有父节点的子节点,其数值均不小于父节点的值;这样层层递归,就是根节点的值最小。
大顶堆:同理,又有一棵完全二叉树,对于任意一个子节点来说,均不大于其父节点的值,如此递推,就是根节点的值是最大的
算法分析
1.假设结果要递增有序。
2.首先用前n个元素的无序序列,构建成大顶堆;
3.构建大顶堆时,从最后一个非叶节点n/2-1的位置开始检查节点与其孩子值是否满足大顶堆的要求,不满足则需要调整该元素与其孩子节点元素的位置,如果有调整,则调整过的孩子节点(子树)也要递归调用调整子树中的元素值位置,保证子树也是大顶堆。然后按照层次遍历的顺序依次往前调整所有非叶节点的值,最后根节点的值就是最大值。
4.得到大顶堆后将根节点与数组待排序部分的最后一个元素交换位置,即将最大元素"沉"到数组末端;
5.交换过后待排序数组长度减一,再对新长度的待排序数组重复上述过程,直到整个数组排序完成。如果我们要数组整体递增有序,则每次构建的是大顶堆;如果我们要数组整体递减有序,则每次构建的是小顶堆。
举例
现在对于堆排序来说,我们先要做的是,把待排序的一堆无序的数,整理成一个大根堆,或者小根堆,下面讨论以大根堆为例子。
给定一个列表array=[4,6,8,5,9],对其进行堆排序(使用大根堆)
构造初始堆:将给定的无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)
此时从最后一个非叶子节点开始(叶子节点不需要调整,第一个非叶子节点arr.length/2-1=5/2-1=1,也就是下面的6节点),从左至右,从上至下进行调整。
此时,把6和9比较交换后堆变为
找到第二个非叶子节点4,由于[4,9,8]中9最大,4和9交换,交换后的结果为
这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6后结果为
此时,我们将一个无序列构造成了一个大顶堆
调整堆:接下来将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。
将堆顶元素9和末尾元素4进行交换后的结果为
重新调整结构,使其继续满足堆的定义
再将堆顶元素8与末尾元素5进行交换,得到第二大元素8
后续过程继续进行调整,交换,如此反复进行,最终得到整个序列有序
源代码
package KthElement;
import java.util.Arrays;
public class HeapSort {
public static int[] heapSort(int[] arr) {
if (arr == null || arr.length <= 1) return new int[]{};
buildHeap(arr);//建堆
int len = arr.length;
while (len > 1) {
//把堆顶元素和最后一个元素交换
swap(arr, 0, len - 1);
//交换完成后,去掉最后一个元素
len--;
//重新调整堆的顺序
heapfy(arr, 0, len);
}
return arr;
}
private static void buildHeap(int[] arr) {
//因为是满二叉树,所以最后一个非叶子节点在树的左边,而不是右边
//最后一个非叶子节点:2i+1>=arr.length --> i>=(arr.length-1)/2
for (int i = (arr.length - 1) / 2 - 1; i >= 0; i--) {
heapfy(arr, i, arr.length);
}
}
private static void heapfy(int[] arr, int i, int len) {
while (true) {
int max = i;
int left = 2 * i + 1;//左孩子索引
int right = 2 * i + 2;//右孩子索引
//若左孩子大于最大值.则更新最大值
if (left < len && arr[left] > arr[max])
max = left;
if (right < len && arr[right] > arr[max])
max = right;
if (max == i) {
break;//若已经是大顶堆,则跳出循环
} else {
swap(arr, i, max);//若不是大顶堆,则交换位置
i = max;
}
}
}
private static void swap(int[] arr, int i, int max) {
int temp = arr[i];
arr[i] = arr[max];
arr[max] = temp;
}
public static void main(String[] args) {
int[] ints = {6, 9, 1, 4, 5, 8, 7, 0, 2, 3};
System.out.println("排序前:" + Arrays.toString(ints));
System.out.println("排序后:" + Arrays.toString(heapSort(ints)));
}
}
时间复杂度:T(n) = O(n log n)
稳定行:不稳定
使用场景:n大时较好