堆排序算法学习小记

1.完全二叉树的概念

若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。

完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。

(1)所有的叶结点都出现在第k层或k-l层(层次最大的两层)

(2)对任一结点,如果其右子树的最大层次为L,则其左子树的最大层次为L或L+l。

一棵二叉树至多只有最下面的两层上的结点的度数可以小于2,并且最下层上的结点都集中在该层最左边的若干位置上,则此二叉树成为完全二叉树,并且最下层上的结点都集中在该层最左边的若干位置上,而在最后一层上,右边的若干结点缺失的二叉树,则此二叉        树称为完全二叉树。

2.堆的概念

堆(英语:heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:

a.堆中某个节点的值总是不大于或不小于其父节点的值;

b.堆总是一棵完全二叉树。

如果某个节点的值总是小于其父节点的值,称为大根堆(a),如果总是大于其父节点的值,称为小根堆(b)。

3.堆排序原理

a.将排序元素构建成堆(升序采用大顶堆,降序采用小顶堆)

b.取出堆顶元素

c.将剩下元素继续构造成堆

重复b c 步骤即可实现堆排序,下面以array=[4,6,8,5,9]数组的升序排序具体说明过程

第一步:将数组元素映射成完全二叉树,如上图

第二步:找出完全二叉树中序号最大的非叶子节点(这里比较难理解),这个数+1也是这棵数的非叶子节点数量,下面给出推导过程:

可以分两种情形考虑:

①树的最后一个非叶子节点若只有左孩子

②树的最后一个非叶子节点有左右两个孩子

完全二叉树的性质之一是:如果节点序号为i,在它的左孩子序号为2*i+1,右孩子序号为2*i+2。

对于①左孩子的序号为n-1,则n-1=2*i-1,推出i=n/2-1;

对于②左孩子的序号为n-2,在n-2=2*i-1,推出i=(n-1)/2-1;右孩子的序号为n-1,则n-1=2*i+2,推出i=(n-1)/2-1;

很显然,当完全二叉树最后一个节点是其父节点的左孩子时,树的节点数为偶数;当完全二叉树最后一个节点是其父节点的右孩子时,树的节点数为奇数。

根据java语法的特征,整数除不尽时向下取整,则若n为奇数时(n-1)/2-1=n/2-1。

因此对于②最后一个非叶子节点的序号也是n/2-1。

对于上面给出的完全二叉树结构,根据上面的推导,可以计算出最后一个非叶子节点的  序号 lastNoChildNodeIndex = arr.lenth/2-1=5/2-1=1,即这棵树中序号为0和1的节点均为非叶子节点。

第三步:开始将这棵完全二叉树调整为最大堆,从最后一个非叶子节点开始调整,依次往序号较低的叶子节点进行,上面的树有0,1两个叶子节点,首先调整序号为1的叶子节点

调整思路:序号为1的叶子节点值为6,它的两个子节点分别为5和9,明显,三个值中,最大的值为9,要满足最大堆,9和6交换。按理说,交换值后,需要考虑序号4即6的位置是否满足最大堆要求,但是由于序号4处是叶子节点,所以满足要求。

第四步:调整序号为0的非叶子节点

调整思路:

0号节点的值以及两个子节点三个值中,明显9最大,交换4和9的值,此时,被交换过的节点1是非叶子节点,且不满足最大堆要求,下面继续调整节点1使其满足最大堆的要求。

第五步:节点1的三个值中,明显6的值最大,交换6和4的值。

到这里一个最大堆就建立起来了。堆顶元素9即为整个数组的最大值。

第六步:将9和4进行交换,其实就是把9放在树的末尾节点,9后面就不参与排序了。

第七步:将剩下的节点用上述方式重新调整为最大堆

第八步:将堆顶元素8和最后的子节点元素5交换,此时,第二大元素8产生,交换后不再参与排序。

重复上面上述的构建最大堆,将最大元素交换到树节点尾,最后得到的结果如下:

到此,完成了所有排序。

4.堆排序java代码实现

public class heapSorted {

    public static void main(String[] args) {
        int[] arr = {4, 6, 8, 5, 9};
        for(int i = 0;i< arr.length;i++){
            System.out.println(arr[i]);
        }
        arr =  sortHeap(arr);

        for(int i = 0;i< arr.length;i++){
            System.out.println(arr[i]);
        }

    }

    private static int[] sortHeap(int[] arr) {
        int len = arr.length;
        //构建最大堆
        buildMaxHeap(arr, len);

        for (int i = len - 1; i > 0; i--) {
            //堆顶元素(序号为0)和当前堆的最后一个节点(序号i)交换
            swap(arr, 0, i);
            //堆元素总数减一,即将当前树结构最后一个节点排除,不参与下一轮堆结构构建
            len--;
            //重新构建大顶堆
            reBuildHeap(arr, 0, len);
        }
        return arr;
    }

    /**
     * 构建大顶堆
     * @param arr
     * @param len
     */
    private static void buildMaxHeap(int[] arr, int len) {
        //(int) Math.floor(len >> 1) = len/2-1 即最大非叶子节点的序号
        for (int i = (int) Math.floor(len >> 1); i >= 0; i--) {
            //从最大非叶子节点开始,依次调整每个非叶子节点,使其满足最大堆要求(比如说最大非叶子节点序号为3,那么一次要调整的非叶子节点为3、2、1、0)
            //调整完毕,最大堆就构建完成
            reBuildHeap(arr, i, len);
        }
    }

    /**
     * 堆调整
     *
     * @param arr
     * @param i
     * @param len
     */
    private static void reBuildHeap(int[] arr, int i, int len) {
        //1. i为传进来的非子树节点的序号
        //2. len为排序数组的长度
        //3. left为i节点的左子树节点序号
        int left = 2 * i + 1;
        //4. right为i节点的右子树节点序号
        int right = 2 * i + 2;
        //5. largest为i、left、right三个节点中值最大的节点序号,暂时假定i节点值最大
        int largest = i;
        //6.如果左节点存在并且左节点的值大于最大值节点(目前为i)的值,则将左节点序号赋给值最大节点序号
        if (left < len && arr[left] > arr[largest]) {
            largest = left;
        }
        //7.如果右节点存在并且右节点的值大于最大值节点的值,则将右节点序号赋给值最大节点序号
        if (right < len && arr[right] > arr[largest]) {
            largest = right;
        }
        //8.上述两个判断,其实就是为了找到三个节点中,值最大的序号的值largest;

        if (largest != i) {
            //9.i和largest不相等,说明i,right,left三个节点中,其中一个子节点(left或者right)比父节点i大,因为是大顶堆,此时要交换父节点和最大子节点的值
            swap(arr, i, largest);
            //10.由于交换了父亲节点和其中一个子的位置,所以被交换成父亲值的子节点可能不满足最大堆的要求,因此重建该子节点的堆结构
            //注意:此时的largest序号处变成了父亲节点的值,也就是说,他已经不再是最大值!!
            reBuildHeap(arr, largest, len);
        }

    }

    /**
     * 值交换函数
     *
     * @param arr
     * @param i
     * @param j
     */
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

5.总结

要完全理解堆排序需要深刻搞懂一下几点

a.什么是堆

b.堆排序的思想

c.序号最大的非叶子节点的计算原理

d.怎么将堆顶元素提取出来

f.提取出堆顶元素后,剩下的元素怎么再构建成堆

备注:文章中的图都是从网上找的,代码也是参考敲了一遍,理解了再敲一遍更容易理解~

原文地址:https://www.cnblogs.com/green-technology/p/heap_sorted.html

时间: 2024-08-29 05:40:09

堆排序算法学习小记的相关文章

KM算法学习小记:

KM算法用于解决二分图最大权匹配问题,这个问题应该是可以用费用流就解决的. 近期遇到了用KM算法去解不等式的题,虽然转换完后还是可以用费用流做,学习中感觉到顶标挺有用的. 学习自: https://blog.csdn.net/c20180630/article/details/71080521 https://www.cnblogs.com/huyufeifei/p/10350763.html 假设我们解决的是最大权完全匹配问题,非完全匹配之后再讨论怎么做. 设\(a[i][j]\)为左边第i个

BSGS算法学习小记(大步小步算法)

简介 先看一个式子xy≡z(modp),z是质数 现在只知道x和z,要求y. 大步小步算法(BSGS,Baby Steps Giant Steps)就是解决这个问题. 算法流程 暴搜的枚举范围 根据费马小定理:xz?1≡1. 如果y已经枚举到了z-1了,继续枚举的话就会产生循环. 所以,在暴搜中y的枚举范围就是0--z-1. 如何优化暴搜 我们想一想可不可以用分块来解决枚举的y. 把y分成p?1????√分别枚举行不行? 设m=p?1????√,y=a?m+b,这样枚举a和b就相当于分块枚举了.

带修改的莫队算法学习小记

简介 莫涛大神创造出的离线询问算法的带修改版. 算法基础:需要掌握莫队算法,会打暴搜(暴力). 一个叫莫的双端队列. 只支持单点修改 操作方法 普通的不带修改的莫队算法要把每个询问带上两个关键字排序,现在待修改的莫队算法要带上三个关键字排序. 初始操作 fo(i,1,m) { scanf("%s%d%d",s,&k,&l); if (s[0]=='Q')a[++tot].l=k,a[tot].r=l,a[tot].x=num,a[tot].p=tot; else d[+

莫队算法学习小记

算法创始人 莫涛大神. 莫涛队长的算法,%%%%%%%%% 算法简介 算法前提 可以在O(1)的时间内把[l,r]的询问转移到[l-1,r],[l+1,r],[l,r-1],[l,r+1]的询问,而且不需要修改操作,那么就可以使用莫队算法([a,b]表示从a到b的区间,包含a和b) 算法核心 假如有一个询问[l,r]要转移到一个询问[l1,r1],那么需要的时间为O(|l1?l|+|r1?r|),在算法前提下,可以用这么多的时间暴力转移. 但是可以发现有时候有些点会被来回算很多次,这样大量浪费了

Cipolla算法学习小记

转自:http://blog.csdn.net/doyouseeman/article/details/52033204 简介 Cipolla算法是解决二次剩余强有力的工具,一个脑洞大开的算法. 认真看懂了,其实是一个很简单的算法,不过会感觉得出这个算法的数学家十分的机智. 基础数论储备 二次剩余 首先来看一个式子x2≡n(modp),我们现在给出n,要求求得x的值.如果可以求得,n为mod p的二次剩余,其实就是n在mod p意义下开的尽方.Cipolla就是一个用来求得上式的x的一个算法.

排序算法学习之堆排序

一.堆与堆排序的产生及定义 在简单选择排序中,每次从n个元素中比较n-1次选取最小的元素,这很好理解,但是前面比较过的数据在之后还要重新比较,这将花费大量的运算时间.堆排序算法就很好的解决了这个问题,堆排序在每次选择到最小记录的同时会根据比较结果对其他数据进行调整,堆排序的时间复杂度为O(NlogN). 堆通常是指二叉堆,即堆是一颗完全二叉树,同时其满足一定性质:每个节点的值大于等于其左右孩子的值(大顶堆),或者每个节点的值小于等于其左右孩子的值(小顶堆).堆在本质上是一个数组,根节点即为a[0

[算法学习笔记]排序算法——堆排序

堆排序 堆排序(heapsort)也是一种相对高效的排序方法,堆排序的时间复杂度为O(n lgn),同时堆排序使用了一种名为堆的数据结构进行管理. 二叉堆 二叉堆是一种特殊的堆,二叉堆是完全二叉树或者是近似完全二叉树.二叉堆满足堆特性:父节点的键值总是保持固定的序关系于任何一个子节点的键值,且每个节点的左子树和右子树都是一个二叉堆. 如上图显示,(a)是一个二叉堆(最大堆), (b)是这个二叉堆在数组中的存储形式. 通过给个一个节点的下标i, 很容易计算出其父节点,左右子节点的的下标,为了方便,

linux学习小记 (一 )

shell 学习小记: 注意:多看系统脚本  多模仿    su切换用户时需要输入目标用户密码,root(superuser)切换到任何用户都不需要输入密码,- 参数必须要是最后一个(su huhu -) sudo需要输入当前用户密码,拥有sudo特权的用户可以执行 "sudo su -"命令,使用自己的密码切换到root用户 , 所以应该在/etc/sudoers 文件中禁止 sudo 执行su命令 linux文件与颜色: /etc/DIR_COLORS   (命令dircolors

堆排序的学习

几句废话先: 本人最近在学习研究数据结构和算法,之所以为什么学习它,第一:是我看到到了现在学习的瓶颈,一直停留在使用别人的什么什么框架或者算法之类的东西,没有对程序更深层次的学习探究.第二:想在IT行业走得更加的长远,数据结构是必须要迈的一道坎. 另外本人从上一篇博客开始已经步入javascript,不在或者很少探讨.NET平台相关内容. 主题: arr.heap-size 至于什么事堆,我在这里也不再的啰嗦,阅读本博文,已经默认你认识它,如果您是大牛,请忽视本博文或者找我的错,我很欢迎  :)