Java常见排序算法详解——堆排序

作者: Demo_Yang | 来源:发表于2019-04-11 00:14 被阅读34次

    转载请注明出处:https://www.jianshu.com/p/73ef54fb99f4

    概念

    二叉树

    要了解堆首先得了解一下二叉树,在计算机科学中,二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。
    二叉树的每个结点至多只有二棵子树(不存在度大于 2 的结点),二叉树的子树有左右之分,次序不能颠倒。二叉树的第 i 层至多有 2i - 1 个结点;深度为 k 的二叉树至多有 2k - 1 个结点;对任何一棵二叉树 T,如果其终端结点数为 n0,度为 2 的结点数为 n2,则n0 = n2 + 1。

    树和二叉树的三个主要差别:

    树的结点个数至少为 1,而二叉树的结点个数可以为 0
    树中结点的最大度数没有限制,而二叉树结点的最大度数为 2
    树的结点无左、右之分,而二叉树的结点有左、右之分
    二叉树又分为完全二叉树(complete binary tree)和满二叉树(full binary tree)
    如图:


    满二叉树:一棵深度为 k,且有 2k - 1 个节点称之为满二叉树
    完全二叉树:深度为 k,有 n 个节点的二叉树,当且仅当其每一个节点都与深度为 k 的满二叉树中序号为 1 至 n 的节点对应时,称之为完全二叉树

    堆(二叉堆)可以视为一棵完全的二叉树,完全二叉树的一个“优秀”的性质是,除了最底层之外,每一层都是满的,这使得堆可以利用数组来表示(普通的一般的二叉树通常用链表作为基本容器表示),每一个结点对应数组中的一个元素。

    如下图,是一个堆和数组的相互关系

    二叉堆一般分为两种:最大堆和最小堆。

    最大堆:

    最大堆中的最大元素值出现在根结点(堆顶)
    堆中每个父节点的元素值都大于等于其孩子结点(如果存在)

    最小堆:

    最小堆中的最小元素值出现在根结点(堆顶)
    堆中每个父节点的元素值都小于等于其孩子结点(如果存在)

    原理

    1. 最大堆调整(Max_Heapify):从堆的倒数第一个非叶子节点作调整,使得子节点永远小于父节点。没有必要从叶子节点开始,叶子节点可以看作是已符合堆特点的节点。
    2. 创建最大堆(Build_Max_Heap):将堆所有数据重新排序
    3. 堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整。
      图解:列如我们有原始数字[2 10 9 5 6 1]
      下面我们用堆排序排序
      原始为:

    第一次:

    第二次


    我们得到了

    代码实现:

     /**
         * 堆排序的主要入口方法,共两步。
         */
        public void sort() {
            /*
             *  第一步:将数组堆化
             *  beginIndex = 第一个非叶子节点。
             *  从第一个非叶子节点开始即可。无需从最后一个叶子节点开始。
             *  叶子节点可以看作已符合堆要求的节点,根节点就是它自己且自己以下值为最大。
             */
            int len = array.length - 1;
            int beginIndex = (len - 1) >> 1;
            for (int i = beginIndex; i >= 0; i--)
                maxHeapify(i, len);
            /*
             * 第二步:对堆化数据排序
             * 每次都是移出最顶层的根节点A[0],与最尾部节点位置调换,同时遍历长度 - 1。
             * 然后从新整理被换到根节点的末尾元素,使其符合堆的特性。
             * 直至未排序的堆长度为 0。
             */
            for (int i = len; i > 0; i--) {
                swap(0, i);
                maxHeapify(0, i - 1);
            }
        }
    
        private void swap(int i, int j) {
            int temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }
    
        /**
         * 调整索引为 index 处的数据,使其符合堆的特性。
         *
         * @param index 需要堆化处理的数据的索引
         * @param len 未排序的堆(数组)的长度
         */
        private void maxHeapify(int index, int len) {
            int li = (index << 1) + 1; // 左子节点索引
            int ri = li + 1;           // 右子节点索引
            int cMax = li;             // 子节点值最大索引,默认左子节点。
            if (li > len) return;      // 左子节点索引超出计算范围,直接返回。
            if (ri <= len && array[ri] > array[li]) // 先判断左右子节点,哪个较大。
                cMax = ri;
            if (array[cMax] > array[index]) {  //若“<”这是从大到小
                swap(cMax, index);      // 如果父节点被子节点调换,
                maxHeapify(cMax, len);  // 则需要继续判断换下后的父节点是否符合堆的特性。
            }
        }
    
    

    算法系列:

    冒泡排序
    选择排序
    直接插入排序
    二分插入排序
    希尔排序
    堆排序

    完整代码:

    Java和Kotlin代码我均放在了GitHub上,欢迎Star!

    GitHub地址:https://github.com/yang0range/MyAlgorithm

    欢迎关注公共号

    关注公共号会有更多收获!

    扫一扫,据说年轻、优秀、颜值高的互联网人都在这个群里

    相关文章

      网友评论

        本文标题:Java常见排序算法详解——堆排序

        本文链接:https://www.haomeiwen.com/subject/tnaaiqtx.html