简介
堆排序(Heapsort)是指利用 二叉堆 这种数据结构所设计的一种排序算法。堆排序的适用数据结构为数组。
工作原理
本质是建立在堆上的选择排序。
排序过程
首先建立大顶堆,然后将堆顶的元素取出,作为最大值,与数组尾部的元素交换,并维持残余堆的性质;
之后将堆顶的元素取出,作为次大值,与数组倒数第二位元素交换,并维持残余堆的性质;
以此类推,在第 n − 1 n-1 n−1次操作后,整个数组就完成了排序。
在数组上建立二叉堆
从根节点开始,依次将每一层的节点排列在数组里。
于是有数组中下标为 i i i 的节点,对应的父结点、左子结点和右子结点如下:
iParent(i) = (i - 1) / 2;
iLeftChild(i) = 2 * i + 1;
iRightChild(i) = 2 * i + 2;
性质
稳定性
同选择排序一样,由于其中交换位置的操作,所以是不稳定的排序算法。
时间复杂度
堆排序的最优时间复杂度、平均时间复杂度、最坏时间复杂度均为 O ( n log ( n ) ) O(n\log(n)) O(nlog(n))
空间复杂度
而由于可以在输入数组上建立堆,所以这又是一个原地算法。
代码
大顶堆
public static void heapSort(int[] arr) {
// 构建初始大顶堆
buildMaxHeap(arr);
for (int i = arr.length - 1; i > 0; i--) {
// 将最大值交换到数组最后
swap(arr, 0, i);
// 调整剩余数组,使其满足大顶堆
maxHeapify(arr, 0, i);
}
}
/**
* 构建初始大顶堆
*
* @param arr
*/
public static void buildMaxHeap(int[] arr) {
// 从最后一个非叶子结点开始调整大顶堆,最后一个非叶子结点的下标就是 arr.length / 2-1
for (int i = arr.length / 2 - 1; i >= 0; i--) {
maxHeapify(arr, i, arr.length);
}
}
/**
* 调整大顶堆
*
* @param arr
* @param i
* @param heapSize 剩余未排序的数字的数量,也就是剩余堆的大小
*/
public static void maxHeapify(int[] arr, int i, int heapSize) {
// 左子结点下标
int l = 2 * i + 1;
// 右子结点下标
int r = l + 1;
// 记录根结点、左子树结点、右子树结点三者中的最大值下标
int largest = i;
// 与左子树结点比较
if (l < heapSize && arr[l] > arr[largest]) {
largest = l;
}
// 与右子树结点比较
if (r < heapSize && arr[r] > arr[largest]) {
largest = r;
}
if (largest != i) {
// 将最大值交换为根结点
swap(arr, i, largest);
// 再次调整交换数字后的大顶堆
maxHeapify(arr, largest, heapSize);
}
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
小顶堆
/**
* 构建初始小顶堆
*
* @param arr
*/
private static void buildMinHeap(int[] arr) {
// 从最后一个非叶子结点开始调整小顶堆,最后一个非叶子结点的下标就是 arr.length / 2-1
for (int i = arr.length / 2 - 1; i >= 0; i--) {
minHeapify(arr, i, arr.length);
}
}
/**
* 调整小顶堆
*
* @param arr
* @param i
* @param heapSize 剩余未排序的数字的数量,也就是剩余堆的大小
*/
private static void minHeapify(int[] arr, int i, int heapSize) {
// 左子结点下标
int l = 2 * i + 1;
// 右子结点下标
int r = l + 1;
// 记录根结点、左子树结点、右子树结点三者中的最小值下标
int smallest = i;
// 与左子树结点比较
if (l < heapSize && arr[l] < arr[smallest]) {
smallest = l;
}
// 与右子树结点比较
if (r < heapSize && arr[r] < arr[smallest]) {
smallest = r;
}
if (smallest != i) {
// 将最小值交换为根结点
swap(arr, i, smallest);
// 再次调整交换数字后的小顶堆
minHeapify(arr, smallest, heapSize);
}
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}