排序算法之5:堆排序 HeapSort

研究了半天,一步一步试验DEBU,才明白堆排序的原理,整理记录一下;
相关参考:
排序算法之堆排序(Heapsort)解析
堆排序及其分析


堆排序(Heapsort)是指利用[堆]这种数据结构所设计的一种[排序算法]。堆积是一个近似[完全二叉树]的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

堆节点的访问

通常堆是通过一维数组来实现的。在数组起始位置为 0 的情形中:

  • 父节点 i 的左子节点在位置 (2*i+1);
  • 父节点 i 的右子节点在位置 (2*i+2);
  • 子节点 i 的父节点在位置 floor((i-1)/2);

堆的操作

在堆的数据结构中,堆中的最大值总是位于根节点 (在优先队列中使用堆的话堆中的最小值位于根节点)。堆中定义以下几种操作:

  • 最大堆调整(Max_Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点
  • 创建最大堆(Build_Max_Heap):将堆所有数据重新排序
  • 堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的[递归]运算
public class HeapSort {

    public static void heapSort(int[] arr) {
        if (arr == null || arr.length == 0)
            return;

        //建立大顶堆
        for (int i = arr.length / 2; i >= 0; i--) {
            heapAdjust(arr, i, arr.length - 1);
        }

        for (int i = arr.length - 1; i >= 0; i--) {
            swap(arr, 0, i);
            heapAdjust(arr, 0, i - 1);
        }
    }

    /**
     * 堆筛选,除了start之外,start~end均满足大顶堆的定义。
     * 调整之后start~end称为一个大顶堆。
     *
     * @param arr   待调整数组
     * @param start 起始指针
     * @param end   结束指针
     */
    public static void heapAdjust(int[] arr, int start, int end) {
        int currentParentValue = arr[start];

        //左右孩子的节点分别为2*i+1,2*i+2
        
        //检测当前节点是否有左孩子
        for (int leftChildIndex = getLeftChildIndexFromParent(start); 
             leftChildIndex <= end; leftChildIndex *= 2) {
            
            int maxChildIndex = leftChildIndex;
            //如果当前左孩子不是末尾元素
            if (leftChildIndex < end) {
                //如果左孩子小于右孩子
                if (arr[leftChildIndex] < arr[leftChildIndex + 1]) {
                    //获取右孩子下标
                    maxChildIndex = leftChildIndex + 1;
                }
            }
            
            //比较当前父节点和最大孩子节点
            if (currentParentValue >= arr[maxChildIndex]) {
                break; //已经为大顶堆,=保持稳定性。
            }
            
            //将最大子孩子移动到其父节点位置
            arr[start] = arr[maxChildIndex]; //将子节点上移
            
            start = maxChildIndex; //下一轮筛选
        }

        arr[start] = currentParentValue; //插入正确的位置
    }

    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public static int getLeftChildIndexFromParent(int parentIndex) {
        return parentIndex * 2 + 1;
    }
}

白话分析排序过程

举例:
存在数组 A = {1, 3, 4, 5, 7, 2, 6, 8, 0},按如下步骤执行:

  1. 构造初始堆,即根据待排序序列构造第一个大根堆或者小根堆;
  2. 首尾交换,断尾重构,即对断尾后剩余部分重新构造大(小)根堆
  3. 重复第二步,直到首尾重叠,排序完成

下面是图解:

建堆图解.png

构建初始大顶堆

从下标n/2处遍历 n/2 - 0 范围的节点进行构建符合堆特性的堆,这里使用大顶堆,即根节点为最大的数字;数组长度为9,即先从 9/2 即 A[4]处开始遍历。

  1. 图1.1:A[4]元素为7,判断7是否有左右子节点,当前无左右子节点,跳转至A[3]。

  2. 图1.1:A[3]元素为5,当前节点有左右子节点,取较大子节点,此时A[7]元素8较大,那么交换A[3]和A[7]即交换数字5和8。交换后迭代遍历被交换的子节点是否也有子节点,如果有,则执行相同的操作,直至被交换的子节点没有孩子节点为止,此处A[7]位置5没有子节点。

  3. 图1.2:A[2]元素为4,且有左右子节点,其中A[6]元素6较大,交换A[2]和A[6]即交换数字4和6。交换后A[6]元素没有子节点,该轮结束。

  4. 图1.3和1.4:A[1]元素为3,和子节点较大者A[3]元素8交换;交换后A[3]元素3和其子节点A[7]元素5交换;交换后A[7]没有子节点,该轮结束。

  5. 图1.5: 最后轮到A[0]元素为1,和子节点较大者A[1]元素8交换;交换后A[1]元素1和子节点较大者A[4]元素7进行交换。交换后A[4]元素7没有子节点,该轮结束。

此时,大顶堆的构建流程完毕。

如下代码中:heapAdjust对当前堆重建之后可以保证根节点为最大值。

        //建立大顶堆
        for (int i = arr.length / 2; i >= 0; i--) {
            heapAdjust(arr, i, arr.length - 1);
        }
首尾交换以及重建堆.png

首尾交换,断尾重构堆

  • 建堆完成之后,堆如图 1.7 是个大根堆。
  • 将 A[0] = 8 与 A[heapLen-1] 交换, 然后 heapLen 减一,如图 2.1,然后 AdjustHeap(A, heapLen-1, 0),如图 2.2。
  • 如此交换堆的第一个元素和堆的最后一个元素即首尾交换,然后堆的大小 heapLen 减一,对堆的大小为 heapLen 的堆进行调堆即断尾重构
  • 如此循环,直到 heapLen == 1 时停止,最后得出结果如图 3。
排序结果.png

图解:

堆排序.gif
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,601评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,367评论 1 305
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,249评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,539评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,967评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,929评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,098评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,825评论 0 207
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,609评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,796评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,282评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,603评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,277评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,159评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,959评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,079评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,874评论 2 277

推荐阅读更多精彩内容

  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,108评论 0 52
  • 概述:排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    每天刷两次牙阅读 3,706评论 0 15
  • 排序的基本概念 在计算机程序开发过程中,经常需要一组数据元素(或记录)按某个关键字进行排序,排序完成的序列可用于快...
    Jack921阅读 1,362评论 1 4
  • 概述排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的...
    Luc_阅读 2,213评论 0 35
  • 1 序 2016年6月25日夜,帝都,天下着大雨,拖着行李箱和同学在校门口照了最后一张合照,搬离寝室打车去了提前租...
    RichardJieChen阅读 5,016评论 0 12