堆的两条性质:
1、堆是一个完全二叉树
2、每个节点的值都大于其子节点的值为大顶堆;小于其节点的值为小顶堆
只要牢记以上堆的这两条性质,堆排序十分好写的,感觉比快排都好写!!
堆排序的时间复杂度是O(nlogn)
堆排序是不稳定排序:不能保证排序前后相等的元素的顺序一致。
堆的结构
由于堆是一个完全二叉树,可以直接用数组存储。可以非常方便的获取每个节点的左右子节点下标。例如节点 i
,它的左孩子节点下标为 2*i + 1
, 右孩子节点下标为 2*i + 2
。下图是一个示例。
最大堆调整(max_heapify),这是堆最为核心的操作
最大堆调整,max_heapify(i, len, nums)
,其中 i 是当前节点,len
是堆当前的长度,nums
是存储堆的数组。这个函数就是要确保节点 i
的孩子节点要小于当前节点(最小堆相反)。这个函数的逻辑很简单,核心就是取左右孩子节点中较大的孩子节点与当前节点进行比较,如果孩子节点大于当前节点,就做交换,否则就不用操作。当前节点与孩子节点进行交换后,孩子节点的位置得到了更小的值,因此为了维持大顶堆的性质,需要对孩子节点再次做堆化。(下面的这个max_heapify
使用的是递归的方式,其实还可以用迭代实现)
void max_heapify(int i, int len, int[] nums) {
// 如果左右节点存在大于当前节点的值
// 选出最大的子节点与当前节点进行交换
// 交换后由于子节点得到的更小的值
// 需要递归判断子节点是否满足条件
int lc = i*2 + 1;
if(lc >= len) {
// 如果左孩子都超出边界直接返回
return;
}
int maxChild = lc;
int rc = lc + 1;
if(rc < len && nums[rc] > nums[lc]) {
// 如果右孩子存在,则选出右孩子和左孩子较大的孩子节点
maxChild = rc;
}
// 判断较大的孩子节点与当前节点的大小关系
if(nums[maxChild] > nums[i]) {
swap(i, maxChild, nums);
// 递归
max_heapify(maxChild, len, nums);
}
}
void swap(int i, int j, int[] nums) {
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
构建大顶堆(build_max_heap)
刚开始nums
数组处于无序的状态,我们把它处理成大顶堆。再复习我们刚才说的大顶堆的性质:每个节点都比其孩子节点大。堆化的方法也很简单,就是利用上述提到的max_heapify
函数从后往前堆每个节点做max_heapify
即可。
有一个可以优化的地方是,其实我们不用从最后一个节点开始做堆化,而是从最后一个非叶子节点做堆化就可以了。因为叶子节点本来就没孩子啊,做堆化也就没有意义了。获取第一个非叶子节点的方法,假如堆有 n
个元素,那么最后一个元素的下标是 n-1
,最后一个元素的父节点就是第一个非叶子节点,它的下标是 (n-2)>>>1
。这个如果记不住也没关系,举个例子手玩一下很快就知道了。
// 创建最大堆
void build_max_heap(int[] nums) {
int n = nums.length;
// 第一个非叶子节点
for(int i = (n-1-1)>>1;i >=0;i --) {
// 从最后一个节点往前进行堆化
max_heapify(i, n, nums);
}
}
堆排序(heap_sort)
堆排序的思想也很简单,有点像选择排序。大顶堆堆顶的元素一定是堆中最大的元素,每次将堆顶元素与最后一个元素做交换,并把交换后的最后一个元素排除到堆外(简单来说就是后面做堆化的时候不要管这个节点,因为它已经在它该在的位置了)。由于堆顶获得了较小的元素,为了维持大顶堆的性质对堆顶做max_heapify
操作。之后重复这样的操作,直到堆中只有一个元素。
下面这个网站是有堆排序的可视化,十分直观,强烈推荐点开看一下
堆排序demo : https://www.cs.usfca.edu/~galles/visualization/HeapSort.html
void heap_sort(int[] nums) {
// 堆排序
// 首先建堆
build_max_heap(nums);
int n = nums.length;
for(int i = n-1;i > 0;i --) {
// 交换堆顶元素到最后
swap(0, i, nums);
// 从堆顶开始堆化
max_heapify(0, i, nums);
}
}
以上就是堆排序的全部内容啦,是不是很简单呢。个人感觉是要比快排好写的。快排还分单路、双路、三路快排。还要对完全顺序、完全逆序、相等元素做优化,而堆排序就不用!
参考资料:
https://zhuanlan.zhihu.com/p/124885051