排序算法:堆排序

1,堆排序基本介绍

  • 堆排序是利用堆这种数据结构设计的一种排序算法,类似与选择排序,它的最慢,最好,平均时间复杂度都是O(nlogn),是不稳定排序
  • 堆是具有以下性质的完全二叉树:每个节点的值都大于或者等于它的左右子节点的值,称为大顶堆;每个节点的值都小于或者等于左右的值,称为小顶堆注意此处没有要求左右节点的顺序关系
    在这里插入图片描述
  • 一般升序使用大顶堆,降序使用小顶堆

2,堆排序基本思想

  • 首先根据大顶堆的基本格式,将无序数组转换为符合大顶堆规则的数组
    • 此处转换先根据算法获取到最后一个非叶子节点index = arr.length / 2 - 1,以该节点为起点,向前依次遍历非叶子节点,并与左右子节点进行递归比较,依次保证以该节点为顶节点的自身大顶堆化
    • 上一步循环处理完成后,保证整个无序数组转换为大顶堆化的数组
  • 数组完成大顶堆化转换完成后,此时顶层节点一定是该部分数组的最大数据,将该数据与处理部分数组的最后一个元素进行替换,即类似选择排序,将最大元素放在数组末尾,并用前部分数组继续进行判断
  • 第一次进行最大元素转换后,此时将小元素转换到大顶堆二叉树的顶部, 该元素非最大元素,但除该元素外的其他部分都符合大顶堆规则,此时只需要对该元素下沉到合适位置,并将大元素上浮,上浮到顶层的元素即为剩余数组部分的最大元素
  • 重复第二步操作直到整个数组完成排序

3,堆排序图解说明

  • 首先,初始化一个数组 {4,6,8,5,9},并将其转换为顺序存储二叉树
    在这里插入图片描述
  • 然后,找到它的最后一个叶子节点索引index = length / 2 - 1 = 4 / 2 - 1 = 1,并以该索引为数据与它的左右节点进行比较,如果左右节点存在大于它的数据,则下沉交换;此处可以看到,6 < 9交换位置,此处注意,6到9的位置后,如果还存在子节点,则需要递归处理,紧跟着会看到
    在这里插入图片描述
  • 索引1处理完成后,继续往前找,找到下一个非叶子节点索引0,用值4和值9比较,肯定9 > 4,继续替换;此时替换后注意,节点4存在两个子节点5和6,而4小于子节点,不满足大顶堆
    在这里插入图片描述
  • 因为以4为顶点的子树不满足大顶堆,则递归进行处理,让4下沉
    在这里插入图片描述
  • 到此为止,由无序数组转为大顶堆数组已经构建完成,例子简单但可以说明问题
  • 现在可以开始进行排序了,构造成大顶堆数据后,root节点即0索引位置数据肯定是最大数据,与选择排序算法基本一致,将该数据与最后一个数据互换位置
    在这里插入图片描述
  • 互换后可以发现,将数组分为了左侧数据和右侧数据两部分,左侧数据为待排数组,右侧数据为有序数组,当左侧数组全部归到右侧后,则整个排序完成,那继续往下走;下一步需要排序的数组,就只需要对左侧数组排序,然后替换底层节点,依次类推
  • 交换位置后,最下层的节点4取代了最上层的节点9的位置,此时大顶堆树混乱;但需要注意的是,此时的混乱是在规则基础上的混乱,也就是只存在顶层节点这一个点是混乱,只需要将该点下沉到合适的位置,并在下沉过程中,将较大的值上浮,等有序后,顶层节点依旧为该数据部分的最大值,则再次与原数组的倒数第二个值替换
    在这里插入图片描述
  • 按照此逻辑继续,直到数组有序
    在这里插入图片描述

4,代码实现

package com.self.datastructure.sort;

import java.util.Arrays;

/**
 * 堆排序
 *
 * @author PJ_ZHANG
 * @create 2020-03-25 9:39
 **/
public class HeapSort {

    public static void main(String[] args) {
        // int array[] = {4, 6, 8, 5, 9, -1, 3, 1, 20, 2, 7, 30, 5, 8, 6, 3, 1};
        // 10万个数测试,  23ms
        // 100万, 291ms
        // 1000万, 3691ms
        int[] array = new int[10000000];
        for (int i = 0; i < 10000000; i++) {
            array[i] = (int) (Math.random() * 8000000);
        }
        long startTime = System.currentTimeMillis();
        heapSort(array);
//        System.out.println(Arrays.toString(array));
        System.out.println("cast time : " + (System.currentTimeMillis() - startTime));
    }

    /**
     * 堆排序:
     * 堆排序基本思想为顺序存储二叉树
     * 对某一个存在字节点的节点来说, index从0开始
     *  * k = 2 * index + 1
     *  * rightIndex = 2 * index + 2
     * 同样, 对某一个存在父节点的节点来说
     *  * parentIndex = (index - 1) / 2
     * 在整个顺序存储二叉树中, 最后一个非叶子节点的索引为
     *  * index = arr.length / 2 - 1
     * 以上几组公式是堆排序的基础
     *
     * 堆排序基本规则
     * * 先将数组转换为大顶堆或者小顶堆的格式, 以大顶堆为例
     * * 转换大小顶堆时, 需要从最后一个非叶子节点向前依次比较, 保证父节点大于叶子节点, 直到root节点
     * * 此时基本的大顶堆已经转换完成, 转换完成基本大顶堆是后续处理的基础
     * * 此时root节点肯定是数组中的最大元素, 将root元素与数组的最后一个元素替换
     * * 将原数组长度改为length - 1, 用剩余部分继续重组大顶堆
     * * 因为基本顶堆已经形成, 此时大顶堆只是顶层元素冲突, 只需要对顶层元素持续下沉到合适的位置, 并将大数据上升即可
     * * 以此类推, 直到所以数组元素移到右侧, 则对排序完成
     *
     * @param array
     */
    public static void heapSort(int[] array) {
        // 构造初始化的大顶堆
        // int i = array.length / 2 - 1: 表示拿到最后一个有效非子节点
        // 处理完成后,向前一直获取非子节点进行大顶堆构造,直到获取到顶层节点
        for (int i = array.length / 2 - 1; i >= 0; i--) {
            adjustHeap(array, i, array.length);
        }

        for (int i = array.length - 1; i >= 0; i--) {
            // 大顶堆构造完成后, 此时顶层元素, 即第一个元素为该数组端最大值, 与最后一个值交换
            int max = array[0];
            array[0] = array[i];
            array[i] = max;
            // 此时基本大顶堆结构没乱, 但是root节点值为较小值, 只需要对root节点下沉到合适的位置
            // 数组长度为i
            adjustHeap(array, 0, i);
        }
    }

    /**
     * 构造大顶堆
     * @param array 原始数组
     * @param index 需要处理的数据索引
     * @param length 需要处理的数组长度
     */
    public static void adjustHeap(int[] array, int index, int length) {
        // 存储临时值, 进行最后值替换
        int temp = array[index];

        // 根据index节点索引获取到元素的左侧节点索引
        // 一次处理完成后, 如果存在子节点大于该节点, 则将该位置修改为子节点的位置
        // k = (k * 2 + 1) 即将k替换为左侧节点, 继续下沉判断
        for (int k = index * 2 + 1; k < length; k = (k * 2 + 1)) {
            // 此处找到左右节点较大的元素
            if (k + 1 < length && array[k] < array[k + 1]) {
                k++;
            }
            // 元素大于目标值, 直接将目标值换位较大的节点
            if (array[k] > temp) {
                // 此处替换后, 当前节点与子节点的值一致, 为之前子节点的值, 被覆盖的值在temp中存储
                array[index] = array[k];
                // 将传递的index参数继续往下推, 与较大节点的子节点继续进行匹配, 判断是否继续下推
                // 此处注意, 持续覆盖值后, index的位置一致被k值修改下推, 最后值就是最初指定的数据需要下沉的位置
                index = k;
            } else {
                break;
            }
        }
        // 在循环里面处理完成后, 将temp下沉到合适的位置
        array[index] = temp;
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值