堆排序算法(图解详细流程)

来自:CSDN,作者:阿顾同学

链接:https://guguoyu.blog.csdn.net/article/details/8128399

堆排序的时间复杂度O(N*logN),额外空间复杂度O(1),是一个不稳定性的排序

一 准备知识

堆的结构可以分为大根堆和小根堆,是一个完全二叉树,而堆排序是根据堆的这种数据结构设计的一种排序,下面先来看看什么是大根堆和小根堆

1.1  大根堆和小根堆

性质:每个结点的值都大于其左孩子和右孩子结点的值,称之为大根堆;每个结点的值都小于其左孩子和右孩子结点的值,称之为小根堆。如下图

我们对上面的图中每个数都进行了标记,上面的结构映射成数组就变成了下面这个样子

还有一个基本概念:查找数组中某个数的父结点和左右孩子结点,比如已知索引为i的数,那么

  1. 父结点索引:(i-1)/2(这里计算机中的除以2,省略掉小数)
  2. 左孩子索引:2*i+1
  3. 右孩子索引:2*i+2

所以上面两个数组可以脑补成堆结构,因为他们满足堆的定义性质:

大根堆:arr(i)>arr(2*i+1) && arr(i)>arr(2*i+2)

小根堆:arr(i)<arr(2*i+1) && arr(i)<arr(2*i+2)

二 堆排序基本步骤

基本思想:

  1. 首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端
  2. 将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1
  3. 将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组

2.1 构造堆

将无序数组构造成一个大根堆(升序用大根堆,降序就用小根堆)

假设存在以下数组

主要思路:第一次保证0~0位置大根堆结构(废话),第二次保证0~1位置大根堆结构,第三次保证0~2位置大根堆结构...直到保证0~n-1位置大根堆结构(每次新插入的数据都与其父结点进行比较,如果插入的数比父结点大,则与父结点交换,否则一直向上交换,直到小于等于父结点,或者来到了顶端

插入6的时候,6大于他的父结点3,即arr(1)>arr(0),则交换;此时,保证了0~1位置是大根堆结构,如下图:

友情提示:待交换的数为蓝色,交换后的数为绿色

插入8的时候,8大于其父结点6,即arr(2)>arr(0),则交换;此时,保证了0~2位置是大根堆结构,如下图

插入5的时候,5大于其父结点3,则交换,交换之后,5又发现比8小,所以不交换;此时,保证了0~3位置大根堆结构,如下图

插入7的时候,7大于其父结点5,则交换,交换之后,7又发现比8小,所以不交换;此时整个数组已经是大根堆结构

2.2 固定最大值再构造堆

此时,我们已经得到一个大根堆,下面将顶端的数与最后一位数交换,然后将剩余的数再构造成一个大根堆

友情提示:黑色的为固定好的数字,不再参与排序

此时最大数8已经来到末尾,则固定不动,后面只需要对顶端的数据进行操作即可,拿顶端的数与其左右孩子较大的数进行比较,如果顶端的数大于其左右孩子较大的数,则停止,如果顶端的数小于其左右孩子较大的数,则交换,然后继续与下面的孩子进行比较。

下图中,5的左右孩子中,左孩子7比右孩子6大,则5与7进行比较,发现5<7,则交换;交换后,发现5已经大于他的左孩子,说明剩余的数已经构成大根堆,后面就是重复固定最大值,然后构造大根堆

如下图:顶端数7与末尾数3进行交换,固定好7,

剩余的数开始构造大根堆 ,然后顶端数与末尾数交换,固定最大值再构造大根堆,重复执行上面的操作,最终会得到有序数组

三 总结

到这里,大家应该对堆排序都有了自己的见解,我们对上面的流程总结下:

  • 首先将无需数组构造成一个大根堆(新插入的数据与其父结点比较)
  • 固定一个最大值,将剩余的数重新构造成一个大根堆,重复这样的过程

四 代码

代码中主要两个方法:

  • 将待排序数组构造成一个大根堆(元素上升)
  • 固定一个最大值,将剩余的数再构造成一个大根堆(元素下降)
    //堆排序
    public static void heapSort(int[] arr) {
        //构造大根堆
        heapInsert(arr);
        int size = arr.length;
        while (size > 1) {
            //固定最大值
            swap(arr, 0, size - 1);
            size--;
            //构造大根堆
            heapify(arr, 0, size);
 
        }
 
    }
 
    //构造大根堆(通过新插入的数上升)
    public static void heapInsert(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            //当前插入的索引
            int currentIndex = i;
            //父结点索引
            int fatherIndex = (currentIndex - 1) / 2;
            //如果当前插入的值大于其父结点的值,则交换值,并且将索引指向父结点
            //然后继续和上面的父结点值比较,直到不大于父结点,则退出循环
            while (arr[currentIndex] > arr[fatherIndex]) {
                //交换当前结点与父结点的值
                swap(arr, currentIndex, fatherIndex);
                //将当前索引指向父索引
                currentIndex = fatherIndex;
                //重新计算当前索引的父索引
                fatherIndex = (currentIndex - 1) / 2;
            }
        }
    }
    //将剩余的数构造成大根堆(通过顶端的数下降)
    public static void heapify(int[] arr, int index, int size) {
        int left = 2 * index + 1;
        int right = 2 * index + 2;
        while (left < size) {
            int largestIndex;
            //判断孩子中较大的值的索引(要确保右孩子在size范围之内)
            if (arr[left] < arr[right] && right < size) {
                largestIndex = right;
            } else {
                largestIndex = left;
            }
            //比较父结点的值与孩子中较大的值,并确定最大值的索引
            if (arr[index] > arr[largestIndex]) {
                largestIndex = index;
            }
            //如果父结点索引是最大值的索引,那已经是大根堆了,则退出循环
            if (index == largestIndex) {
                break;
            }
            //父结点不是最大值,与孩子中较大的值交换
            swap(arr, largestIndex, index);
            //将索引指向孩子中较大的值的索引
            index = largestIndex;
            //重新计算交换之后的孩子的索引
            left = 2 * index + 1;
            right = 2 * index + 2;
        }
 
    }
    //交换数组中两个元素的值
    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

-End-

(0)

相关推荐

  • 什么是冒泡排序?什么是选择排序?它们之间有什么区别?

    什么是冒泡排序?什么是选择排序?它们之间有什么区别?

  • 堆排序

    堆排序

  • 图解:什么是堆排序?

    二叉堆(Binary Heap)是一颗特殊的完全二叉树,一般分为大顶堆和小顶堆,我就不啰嗦啦!具体内容你可以看一下 图解:什么是二叉堆? 堆排序 要学习今天的堆排序(Heap Sort),我们以一个数 ...

  • Python|数组中对连续3个元素求平方和输出最大值

    问题描述对于给出的长度为N的整数数组,对连续3个元素求平方和,输出平方和的最大值.输入说明:第一行,数组中元素个数N(N<1000),第二行是这个数组中的N个元素,中间用空格隔开,每个元素小于1 ...

  • 图解:什么是线段树?

    作者简介:黄子桓,华师大三学生一枚,进场参加各类算法竞赛,并获得了挺多荣誉,耗费 3 天时间给大家贡献此文! 图文:黄子桓 编辑:景禹 致谢:感谢子桓的无私贡献,收到您的投稿已有时日,只是我工作时间紧 ...

  • ​LeetCode刷题实战42:接雨水

    算法的重要性,我就不多说了吧,想去大厂,就必须要经过基础知识和业务逻辑面试+算法面试.所以,为了提高大家的算法能力,这个公众号后续每天带大家做一道算法题,题目就从LeetCode上面选 ! 今天和大家 ...

  • 图解:什么是二叉堆?

    在正式开始学习堆之前,一定要大脑里回顾一下什么是完全二叉树,因为它和堆可是息息相关奥! 如果二叉树中除了叶子结点,每个结点的度都为 2,则此二叉树称为满二叉树. 而如果二叉树中除去最后一层节点为满二叉 ...

  • 项目管理详细流程图解

    我是1045号东创项目管理集团有限公司 快来支持我吧~! 主要内容 项目管理体系是项目管理功能的各要素的集合,包括项目管理组织体系.项目管理办公室PMO.项目管理的流程规范.项目考核.项目管理信息平台 ...

  • 学习药物制剂开发研究详细流程

    学习药物制剂开发研究详细流程

  • .NET之Docker部署详细流程

    dotNET跨平台 今天 以下文章来源于鹏祥 ,作者AZRNG 开篇语 自己从头开始走一遍docker部署.net的流程,作为一种学习总结,以及后续会写一些在该基础之上的文章. 本次示例环境:vs20 ...

  • 公司清算注销详细流程,非常详细!

    如今,公司注册容易,但对公司注销,常有财务人员抱怨,耗时.耗力,太麻烦了.那注销流程要准备多少事?提醒各位:企业不营业了,一定要注销! 01 公司注销,清算流程 1.解散决议 有限责任公司,<公 ...

  • 抓紧!年报时间余额不足,详细流程在这里!

    2020年的年报还有一个多月了,凡是在2020年12月31日及之前注册的公司.个体工商户.专业合作社等,现在开始,都要进行2020年度的年报. 不按规定执行的列入经营异常名录和严重违法失信企业名单,请 ...

  • 中试转生产的详细流程

    引言 中间实验阶段是进一步研究在一定规模的装置中各步化学反应条件的变化规律,并解决实验室中所不能解决或发现的问题.虽然化学反应的本质不会因实验生产的不同而改变,但各步化学反应的最佳反应工艺条件,则可能 ...

  • 商用版砂锅煲汤,虫草花竹荪炖鸡,汤料调制详细流程(下)

    商用版砂锅煲汤,虫草花竹荪炖鸡,汤料调制详细流程(下)

  • 景观照明设计实战教程/华悦城夜景灯光创意//华悦城PS鸟瞰效果图制作详细流程与技法

    景观照明设计实战教程/华悦城夜景灯光创意//华悦城PS鸟瞰效果图制作详细流程与技法

  • 科目三考试全程16项详细流程解析,不想挂科快收藏!

    都说科目三比科目二简单,但是考过科目三的很多人并不这么认为.因为科目三是路考,不仅仅考察技术,对应变能力其实也有要求,而且科目三全程16个考试项目,每个项目又有其需要注意的问题,细节特别多. 今天小驾 ...