堆排序算法学习小记

Posted green-technology

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了堆排序算法学习小记相关的知识,希望对你有一定的参考价值。

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.提取出堆顶元素后,剩下的元素怎么再构建成堆

 

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

以上是关于堆排序算法学习小记的主要内容,如果未能解决你的问题,请参考以下文章

算法排序之堆排序

经典算法学习——堆排序

排序算法学习(直接插入排序,希尔排序,选择排序,堆排序,冒泡排序)

配对堆优化Dijkstra算法小记

十大经典排序算法总结(堆排序)

排序算法堆排序 (算法导论学习)