算法入门5-堆排序

  堆排序,实际是将数组转化成堆,利用堆的结构,保存每一次的对比结果,加快排序的过程。

一、什么是堆

​  堆可以简单理解为是一种有一定顺序的完全二叉树。

​  而完全二叉树就是中间没有空节点的二叉树。

​  完全二叉树图示:

在这里插入图片描述

​  (PS:完全二叉树定义:一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。)

​  堆也是一种完全二叉树,但是它有一个性质:堆中某个节点的值总是不大于或不小于其父节点的值。所以,堆简单记为有一定顺序的完全二叉树。

​  根据堆的性质,分为两种堆。

​  1、根节点是最大值的堆叫做大根堆;

​  2、根节点是最小值的堆叫做小根堆;

​  堆的图示:

在这里插入图片描述

二、堆排序实现过程

​  根据完全二叉树结构,设当前位置为 i ,则

​  当前位置左节点为 (2 * i + 1)

​  当前位置右节点为 (2 * i + 2)

​  当前位置父节点为 ((i - 1) / 2)

​  堆排实现步骤:

​  1、先将数组转化为大根堆,转化思路:将数组0位定为堆的根节点,数组后续的数依次插入这个大根堆,插入规则是,当前插入的值比父节点大时,则当前值与父节点值交换,确保大根堆的结构;

​  2、形成大根堆后,取出该堆的根节点,即数组中最大值,排到数组最后一位,由此,数组中最大值已排好位置。剩下的数继续调整为大根堆,继续取出根节点,直至整个数组排好序。

​  3、上述过程,我们可以不用辅助数组,直接在原数组上操作,作法是,将堆的根节点与堆的最后一个节点交换,并把堆容量-1,即数组0位与数组最后一位交换值,如:原始数组arr是0-9位,取出一次根节点后,数组arr[9]就是最大值,剩下的0-8位继续组成大根堆,继续取出根节点,直到0-0位时,则整个数组有序。

​  过程图示:
在这里插入图片描述

​  java代码实现:

// 堆排
public class HeapSort {

    public static void main(String[] args) {
        Integer[] arr = DataStore.getData();
        String source = "";
        for(int i = 0; i < arr.length; i++) {
            source += arr[i] + " ";
        }
        System.out.println("原数组:" + source);
        HeapSort.sort(arr);
        String res = "";
        for(int i = 0; i < arr.length; i++) {
            res += arr[i] + " ";
        }
        System.out.println("排序后:" + res);
    }

    // 堆排
    public static void sort(Integer[] arr) {
        // 将数组转化为大根堆
        for(int i = 0; i < arr.length; i++) {
            HeapSort.headInsert(arr, i);
        }
        // 堆容量
        int headSize = arr.length - 1;
        while(headSize > 0) {
            // 取出最大值,即将堆的根节点与最后一个堆值交换,并让堆容量-1
            DataStore.swap(arr, 0, headSize);
            headSize = headSize - 1;
            // 调整堆顺序为大根堆
            HeapSort.headIfy(arr, 0, headSize);
        }
    }

    // 向堆中插入新值
    public static void headInsert(Integer[] arr, int i) {
        if(i >= 0) {
            // 堆结构固定,arr[(i - 1) / 2]得到的是父节点
            // 如果当前位置比父节点大,则与父节点交换值,并把当前位置i指向父节点
            // 循环这一过程,直到当前位置的值不比父节点大,或者当前位置已到堆顶
            while(arr[i] > arr[(i - 1) / 2]) {
                DataStore.swap(arr, i, (i - 1) / 2);
                i = (i - 1) / 2;
            }
        }
    }

    // 调整堆顺序为大根堆
    public static void headIfy(Integer[] arr, int i, int headSize) {
        if(i >= 0) {
            // 堆结构固定,(2 * i + 1) 得到的是左子节点
            // 左子节点小于等于堆容量时,即当前位置仍然有子节点时,继续循环
            while((2 * i + 1) <= headSize) {
                // 左子节点
                int l = 2 * i + 1;
                // 堆结构固定,(2 * i + 2) 得到的是右子节点
                int r = 2 * i + 2;
                // 获取左右节点中的最大值
                int large = r <= headSize && arr[r] > arr[l] ? r : l;
                // 获取当前节点与左右子节点间的最大值
                large = arr[large] > arr[i] ? large : i;
                // 当最大值就是当前位置,说明已经是大根堆,跳出循环
                if(large == i) {
                   return;
                }
                // 当前位置的值与最大值交换
                DataStore.swap(arr, i, large);
                // 当前位置指向最大值节点
                i = large;
            }
        }
    }
}

​  DataStore工具类

public class DataStore {

    // 随机获取10个整数
    public static Integer[] getData() {
        List<Integer> list = new ArrayList<Integer>();
        for(int i = 0; i < 10; i++) {
            double d = Math.random();
            list.add((int)(d*10));
        }
        Integer[] arrays = new Integer[list.size()];
        list.toArray(arrays);
        return arrays;
    }

    // 数组中两数交换
    public static void swap(Integer[] array, int i, int j) {
        if(array != null && i < array.length && j< array.length) {
            int temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }
    }
}

​  输出:

​  原数组:5 1 7 7 3 6 5 8 6 8
  ​排序后:1 3 5 5 6 6 7 7 8 8

​  容易看到,堆排将一个数组看成是一个完全二叉树的结构去处理,完全二叉树的层级是logN,每个数最多需要对比的次数也是logN,所以,堆排序时间复杂度是O(N*logN),并且,空间复杂度只有O(1)

​  堆排序真是计算机先驱的一大发明,一个巧妙的排序过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值