一篇文章弄懂——堆排序

概述

堆排序是我们常说的十大排序算法中的一种,堆排序也是数据结构中比较重要的一个知识点,我们今天就来好好探究一下堆排序。在说堆排序之前,我们就必须先明白什么是二叉堆,因为堆排序就是在二叉堆的基础上完成的。

什么是二叉堆

话不多说直接上图:

仔细观察上图的特点:

1. 二叉堆是一颗完全二叉树。

2. 二叉堆中父节点的值总是大于获等于(大顶堆)任何一个孩子节点的值。

3. 每一个结点的左子树和右子树都是一个二叉堆。

在了解二叉堆的特性后我们知道,二叉堆其实就是一颗完全二叉树,那么我们如何在程序中表示这个完全二叉树呢?

使用数组表示完全二叉树

使用数组表示一颗完全二叉树的思路是什么呢?

即按照层次遍历的顺序将二叉树的所有节点存入数组中{1,3,7,5,24,41,12,6,5}。既然使用数组保存二叉树,那么访问某个节点的孩子节点,以及父节点的基本需求还是需要满足的:

假设节点的索引为 i,,那么:

该节点的左孩子的数组索引为:2*i+1

右孩子:2*i+2

父节点:(i-1)/2    整数除法

我们可以验算一下,拿键值为5的那个节点来举例,它的索引为3,那么左孩子就应该是2*3+1,也就是索引为7的值.......

上面我们知道了在进行堆排序之前所需要的知识,那么我们就来了解一下堆排序的具体思路吧!

基于二叉堆如何进行排序呢?

 第一步:将堆顶元素与最后的叶子节点进行交换

此时会发现堆顶元素(二叉树的根节点)不满足小顶堆的要求(根元素要比两个孩子节点都打小),那么此时就需要将根元素进行“下沉”。

第二步:将根元素进行“下沉”

如果发现粉红色的5依然不满足小顶堆的要求,那么粉色5需要继续下沉,同样是与孩子节点的最小值进行交换。

 

第三步:将堆顶元素与倒数第二个元素进行交换

第四步:此时堆顶元素还需要进行下沉(下图是经过多次下沉的结果)

注意:标蓝的节点代表已经排好序的元素,所以在下沉的时候就不要考虑它们。

第五步:将堆顶与倒数第三个进行交换

第六步:下沉........

就这么重复“交换”和“下沉”的步骤,直到数组中的所有元素都有序即可完成堆排序。

我想经过上面的图解,你应该对堆排序的的思路有了清晰的认识,到了这里我们还差最后一步,那就是将一个无序数组进行“堆化”。

数组的“堆化”

我们给定一个数组:{7,5,12,6,24,41,1,3,5}。

将数组图形化为二叉树:

从 (数组长度 / 2) - 1  的位置处开始进行下沉,然后依次往后进行下沉:

图中数组的长度为9,所以从9/2-1,也就是索引为3的位置开始,也就是值为“6”的节点,将“6”下沉到合理位置,如下图:

然后在将索引为2,也就是值为“12”的节点,下沉至合理位置,如下图:

然后将索引为1,也就是值为“5”的下沉至合理位置:

最后将索引为0,也就是根节点下沉至合理位置:

这样一个小顶堆就完成了构建,这样堆排序的所有思路都理解了吧。

思路总结

堆排序的第一步就是将无序数组“堆化”,顺序排序则堆化成大顶堆,若想逆序排序则堆化成小顶堆。

然后在小顶堆或大顶堆的基础上进行排序。排序又可总结成两步:

第一步:交换(根节点与未排序的尾节点交换)

第二步:下沉(交换后的节点可能不满足堆的规则,需要下沉到合理位置)

堆排序的实现

import java.util.Arrays;

public class TestHeapSort {
    public static void main(String[] args) {
        int[] arr = {7, 5, 12, 6, 24, 41, 1, 3, 5};
        heapSort(arr);
        System.out.println(Arrays.toString(arr));
    }


    /**
     * 进行堆排序
     * @param arr
     */
    private static void heapSort(int[] arr){
        makeHeap(arr);//将数组堆化
        sort(arr,arr.length);
    }

    /**
     * 进行无序数组的“堆化操作”
     *
     * @param arr
     */
    private static void makeHeap(int[] arr) {
        int k = arr.length / 2 - 1;//获取堆化操作的起始节点
        for (int i = k; i >= 0; i--) {
            minHeapFixDown(arr, i,arr.length);
        }
    }

    /**
     * 判断以索引为k的节点的二叉树是否满足小顶堆堆,如果不满足,则将这个元素下沉,使其满足小顶堆要求
     *
     * @param arr
     * @param k
     */
    private static void minHeapFixDown(int[] arr, int k,int length) {
        int base = arr[k];
        Integer left = null;
        Integer right = null;
        if (k * 2 + 1 < length) {
            left = arr[k * 2 + 1];
        }
        if (k * 2 + 2 < length) {
            right = arr[k * 2 + 2];
        }
        if (left == null && right == null) {//如果没有孩子节点了,则退出函数,也就是说已经下沉到底部了。
            return;
        }
        if (right != null) {//如果有右孩子
            if (base <= left && base <= right) {//如果满足小顶堆,则退出函数
                return;
            }
            if (left < right) {
                swap(arr, k, 2 * k + 1);
                minHeapFixDown(arr, 2 * k + 1,length);
            } else {
                swap(arr, k, 2 * k + 2);
                minHeapFixDown(arr, 2 * k + 2,length);
            }
        } else {//只有左孩子时
            if (base > left) {
                swap(arr, k, 2 * k + 1);
            }
        }


    }

    /**
     * 在小顶堆的基础上,对数组进行排序,将对顶与最后一个元素进行交换,然后使对顶进行下沉,但下沉的边界不包含刚刚交换的
     * 元素的位置。此时又是一个小顶堆,然后将对顶与倒数吧第二个元素进行交换......(递归)
     * @param arr
     * @param length
     */
    private static void sort(int[] arr,int length){
        if(length==1){
            return;
        }
        swap(arr,0,length-1);
        minHeapFixDown(arr,0,length-1);
        sort(arr,length-1);
    }

    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

 

执行结果:

原创不易啊,看客老爷们帮忙点个赞哈!!!

posted @ 2019-03-04 20:30  听到微笑  阅读(0)  评论(0编辑  收藏  举报  来源