堆排序
1. 基本原理
传送门 堆、堆排序、优先队列
2. 算法步骤
- 由输入的无序数组构造一个最大堆,作为初始的无序区
- 把堆顶元素(最大值)和堆尾元素互换
- 把堆(无序区)的尺寸缩小1,并调用sinking函数,目的是把新的数组顶端数据调整到相应位置
- 重复步骤2,直到堆的尺寸为1
3. 算法图解
3.1 构建二叉堆
构建二叉堆,也就是把一个无序的完全二叉树调整为二叉堆,本质上就是让所有非叶子节点依次下沉。
我们举一个无序完全二叉树的例子:
首先,我们从最后一个非叶子节点开始,也就是从节点10开始。如果节点10大于它左右孩子中最小的一个,则节点10下沉。
接下来轮到节点3,如果节点3大于它左右孩子中最小的一个,则节点3下沉。
接下来轮到节点1,如果节点1大于它左右孩子中最小的一个,则节点1下沉。事实上节点1小于它的左右孩子,所以不用改变。
接下来轮到节点7,如果节点7大于它左右孩子中最小的一个,则节点7下沉。
节点7继续比较,继续下沉。
至此,一颗无序的完全二叉树就构建成了一个最小堆。
3.2 堆排序过程
堆排序是利用堆的自我调整实现的。
例如给定一个数组,对数组进行排序,使数组元素从小到大排列。
首先,我们要根据给定的数组构建一个最大堆。当我们删除一个最大堆的堆顶(并不是完全删除,而是替换到最后面),经过自我调节,第二大的元素就会被交换上来,成为最大堆的新堆顶。
如上图所示,当删除值为10的堆顶节点,经过调节,值为9的新节点就会顶替上来;当删除值为9的堆顶节点,经过调节,值为8的新节点就会顶替上来…
由于二叉堆的这个特性,我们每一次删除旧堆顶,调整后的新堆顶都是大小仅次于旧堆顶的节点。那么我们只要反复删除堆顶,反复调节二叉堆,所得到的集合就成为了一个有序集合,过程如下:
删除节点9,节点8成为新堆顶:
删除节点8,节点7成为新堆顶:
删除节点7,节点6成为新堆顶:
删除节点6,节点5成为新堆顶:
删除节点5,节点4成为新堆顶:
删除节点4,节点3成为新堆顶:
删除节点3,节点2成为新堆顶:
至此,原本的最大堆已经变成了一个从小到大的有序集合。之前说过二叉堆实际存储在数组当中,数组中的元素排列如下:
4. 动画演示
5. 参考实现
import java.util.Arrays;
/**
* @author wylu
*/
public class HeapSort {
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
/**
* 下沉操作
* @param heap 需要调整的堆
* @param i 需要下沉元素的索引
* @param x 需要下沉元素的值
* @param size 当前堆的有效范围
*/
private static void sinking(int[] heap, int i, int x, int size) {
while (2 * i + 1 < size) {
int a = 2 * i + 1, b = 2 * i + 2;
//定位到最大的孩子节点
if (b < size && heap[b] > heap[a]) a = b;
//如果当前节点大于等于最大孩子节点,说明调整完毕
if (heap[a] <= x) break;
heap[i] = heap[a];
i = a;
}
heap[i] = x;
}
/**
* 构建大顶堆
* @param arr 建堆数组
*/
private static void buildMaxHeap(int[] arr) {
for (int i = arr.length / 2 - 1; i >= 0; i--) {
sinking(arr, i, arr[i], arr.length);
}
}
public static void sort(int[] arr) {
//根据无序数组构建二叉堆
buildMaxHeap(arr);
//循环删除堆顶元素,移到数组尾部,调节堆产生新的堆顶
for (int i = arr.length - 1; i > 0; i--) {
//最后一个元素和堆顶元素交换
swap(arr, i, 0);
//下沉调整最大堆
sinking(arr, 0, arr[0], i);
}
}
public static void main(String[] args) {
int[] arr = {3, 1, 4, 9, 6, 0, 7, 2, 5, 8};
HeapSort.sort(arr);
System.out.println(Arrays.toString(arr));
}
}
6. 复杂度分析
排序算法 | 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | 排序方式 | 稳定性 |
---|---|---|---|---|---|---|
堆排序 | O ( n l o g n ) O(nlogn) O(nlogn) | O ( n l o g n ) O(nlogn) O(nlogn) | O ( n l o g n ) O(nlogn) O(nlogn) | O ( 1 ) O(1) O(1) | In-place | 不稳定 |