堆排序算法(图解详细流程)_堆排序的详细过程-程序员宅基地

技术标签: 算法  堆排序  图解过程  堆结构  

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

目录

一 准备知识

1.1  大根堆和小根堆

二 堆排序基本步骤

2.1 构造堆

2.2 固定最大值再构造堆

三 总结

四 代码

 


一 准备知识

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

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,

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

 

三 总结

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

1、首先将无需数组构造成一个大根堆(新插入的数据与其父结点比较)

2、固定一个最大值,将剩余的数重新构造成一个大根堆,重复这样的过程

四 代码

代码中主要两个方法:

1、将待排序数组构造成一个大根堆(元素上升)

2、固定一个最大值,将剩余的数再构造成一个大根堆(元素下降)

    //堆排序
    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;
    }

                                                              友情提示:手机观看,可以左右滑动 

 

 

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u010452388/article/details/81283998

智能推荐

linux下expdp定时备份_Linux 自动备份 Oracle 数据库 Shell 脚本-程序员宅基地

文章浏览阅读755次。我们之前也介绍过,如何在 Linux 备份 MySQL 。作为一个后端开发人员,备份数据库是一个良好的习惯,虽然说数据库损坏或者数据丢失的概率很低,但是你不得不防,要不2019-9-11 10:23:4301.3k我是备份在本机,所以我采用 Oracle 自带的 expdp 功能进行备份;如需要异地的,则需要针对以下脚本进行加工,这里不阐述准备工作本机具有 Oracle 的 expdp 导出功能开..._expdp 备份写时间

android新浪微博登录获取用户信息_微博软件上是也有获取用户lp-程序员宅基地

文章浏览阅读2.6k次。第一步:准备工作 在新浪微博开发者平台http://open.weibo.com/apps/注册并上传应用各种信息(比较多)获取到appkey,关于签名信息最好使用新浪提供的工具省心些,默认的授权回调页https://api.weibo.com/oauth2/default.html要与代码中一致。第二步:下载新浪SDK添加到工程librepositories { flatDi_微博软件上是也有获取用户lp

python的pandas数据处理_pandas minor_xs用法-程序员宅基地

文章浏览阅读424次。1、numpy纯属组,有一维二维三维数组,但是无索引与列名,所以计算速度快2、series一维数组,有标签,(主要是用在时间序列的数据上)3、dataframe二维数据 表格里横向A B ,纵向A B4、panel三维数据 由items major_xs minor_xs 组成,可有multiindex多索引的数据格式(iteritem是每一列的加总,iterrow..._pandas minor_xs用法

超详细---使用QRCode生成二维码并生成到PDF上_java 二维码 添加到doc模板-程序员宅基地

文章浏览阅读5.1k次。突发奇想想生成一个这样的一个带二维码的pdf:然后就开始做了废话不多说了直接上代码:POM.XML(所需要的jar)里面的jar可能不全,根据错误提示需要自己再去引入jar<!-- PDF相关 start --> <dependency> <groupId>org.apache.poi</groupId> ..._java 二维码 添加到doc模板

IDEA 报错 需要class, interface或enum_idea需要class interface或enum-程序员宅基地

文章浏览阅读1.3w次,点赞11次,收藏11次。需要class, interface或enum或者显示非法字符将右下角的UTF-8转换为GBK再将GBK转为UTF-8就OK啦!_idea需要class interface或enum

Numpy基础学习_np.diff python-程序员宅基地

文章浏览阅读476次。文章目录资源一些概念2.1numpy属性2.2numpy的创建array2.3numpy的基础运算资源Numpy & Pandas (莫烦 Python 数据处理教程)一些概念在统计学语言的角度来看,一维数组可以称为vector(向量);而二维数组可以称为matrix(矩阵);三维以上的就没有特殊名称,全都称为array。向量、矩阵都可以看作是数组的特殊形式,被数组这个概率所包含。2.1numpy属性import numpy as nparray = np.array([[1, 2,_np.diff python

随便推点

CSS3中强大的filter(滤镜)属性使用详细解说-程序员宅基地

文章浏览阅读662次。在CSS3新增加的属性中有一个属性较为特殊,那就是filter属性,该属性一般来说是用于元素本身的装饰,使页面产生更多的显示特效。今天我们就来详细了解该属性。亮度属性名称filter:b..._filter:brightness

YYText iOS17兼容问题-程序员宅基地

文章浏览阅读1.4k次。使用YYText ,1.0.7在iOS17会遇到如下崩溃主要原因是UIGraphicsBeginImageContext中的asert引发,而改接口早就标记为API_TO_BE_DEPRECATED,建议使用UIGraphicsImageRenderer替换。由于YYText早已没有维护,于是fork了一个自己的git进行修正,主要修正逻辑在textAsynLayer替换为。_yytext ios17

Ext.data- Connection/Ajax/Record_ext.data.connection beforerequest-程序员宅基地

文章浏览阅读267次。 Ext.data.Connection访问指定的url,这是个异步调用类,如果想得到服务器信息,请在request参数中指定callback方法或指定侦听者(对文件上传无效)Connection( Object config )构造,config定义为{ autoAbort : Boolean, defaultHeaders : Object, disable_ext.data.connection beforerequest

判断移动端是否滚动到底部_移动端 如何判断div是否滚动到了底部-程序员宅基地

文章浏览阅读1k次。<div class="rule_content" @scroll.passive="getScroll($event)"> <p class="rule_title_content">111111111111</p> <p class="rule_title_content">111111111111</p> <p class="rule_title_content">111111111111</p> <._移动端 如何判断div是否滚动到了底部

Android 生成自己的 implementation 依赖_安卓 implementation 写法-程序员宅基地

文章浏览阅读1k次。在开发过程中,有些工具总是重复使用, 可以自己创建个工具包 , 这样就可以在新的项目中直接引用而不需要每次都进行复制粘贴,节省了很多不必要的时间.接下来一步一步实现:第一步需要在工程目录下的 build.gradle 中添加 dependencies 部分classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'第二步在library的build.gradle文件增加apply plugin: 'com.gith_安卓 implementation 写法

java反编译工具jd-gui使用_jd反编译怎么使用-程序员宅基地

文章浏览阅读6.4k次。《JD-GUI》是一款反编译软件,JD分为JD-GUI、JD-Eclipse两种运行方式,JD-GUI是以单独的程序的方式运行,JD-Eclipse则是以一个Eclipse插件的方式运行。基础知识什么是反编译器大家都知道,将源代码转换成二进制执行代码的过程叫“编译”,比如将C源代码编译成exe可执行文件;那么把二进制执行代码的过程就叫“反编译”,比如把exe转换为C源代码就叫“反编译”..._jd反编译怎么使用

推荐文章

热门文章

相关标签