比快排还好写的堆排序,你还不会?

比快排还好写的堆排序,你还不会?

堆的两条性质:

1、堆是一个完全二叉树

2、每个节点的值都大于其子节点的值为大顶堆;小于其节点的值为小顶堆

只要牢记以上堆的这两条性质,堆排序十分好写的,感觉比快排都好写!!

堆排序的时间复杂度是O(nlogn)

堆排序是不稳定排序:不能保证排序前后相等的元素的顺序一致。

堆的结构

由于堆是一个完全二叉树,可以直接用数组存储。可以非常方便的获取每个节点的左右子节点下标。例如节点 i ,它的左孩子节点下标为 2*i + 1, 右孩子节点下标为 2*i + 2。下图是一个示例。

比快排还好写的堆排序,你还不会?_第1张图片

最大堆调整(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

比快排还好写的堆排序,你还不会?_第2张图片

 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

你可能感兴趣的:(算法,数据结构,排序算法)