编程马拉松 Day05 堆、二叉堆、堆排序

堆排序需要用到二叉堆,在开始之前,我们先来了解一下什么是二叉堆。

当二叉树满足满足如下条件时,我们说这个二叉树是堆有序的:

  1. 每一个父结点的值都比它的子结点大(称为大顶堆)或小(称为小顶堆)
  2. 子结点的大小与其左右位置无关

堆有序的二叉树,也可称为二叉堆。二叉堆是最常见的堆结构,因此也常将二叉堆直接称为,可以采用如下两种方式来表示二叉堆

  1. 使用指针,二叉树的每个结点需存储三个指针,分别指向其父结点和两个子结点
  2. 使用数组,对二叉树做层序遍历,按层级顺序放入数组中,根结点在数组索引0的位置存放,其子结点分别在索引1和2的位置,1和2个子结点分别在位置3、4和5、6中存放,以此类推

就排序来讲,其所需处理的数据较为连续,没有空隙,可用完全二叉树来表示。对于完全二叉树,采用数组的表示方法也更方便些,下图展示了采用数组实现的两个二叉堆。


二叉堆

对于数组实现的二叉堆,索引为k的结点的父结点的索引为(k-1)/2,它的子结点的索引分别为2k+1和2k+2。

堆有序化

以大顶堆为例,有序化的过程中我们会遇到两种情况

  1. 在堆底加入一个较大元素时,我们需要由下至上恢复堆的顺序
  2. 当将根结点替换为一个较小元素时,我们需要由上到下恢复堆的顺序

由下至上的堆有序化(上浮)

如果堆的有序状态因为某个结点变的比它的父结点更大而被打破,就需要通过将它与它的父结点交换来恢复堆有序。交换后,这个结点比它的两个子结点都大,但这个结点仍然可能比它现在的父结点更大。我们可以一遍遍的用同样的方式来将其向上移动,直到遇到一个比它更大的父结点或到达了堆的根结点,如下图所示。


由下至上的堆有序化(上浮)

上浮操作对应的代码如下

private void swim(Integer arr[], int k) {
    while(k > 0 && arr[(k - 1) / 2] < arr[k]) { //若k>0且索引为k的结点大于其父结点时,将该结点与其父结点交换
        swap(arr, k, (k - 1) / 2);
        k = (k - 1) / 2;
    }
}

由上至下的堆有序化(下沉)

如果堆的有序状态因为某个结点变的比它的某个子结点更小而被打破,就需要通过将它和它的子结点中较大者交换位置来恢复堆有序。交换可能会在子结点处继续打破堆的有序状态,此时可以采用相同的方式,将结点向下移动直到它的子结点都比它小或是到达了堆的底部,如下图所示。


由上至下的堆有序化(下沉)

下沉操作对应的代码如下

private void sink(Integer arr[], int k) {
    while(2 * k + 1 <= arr.length - 1) {//若k存在子结点,则进入循环
        int j = 2 * k + 1; //获取k的第一个子结点
        if (j < arr.length - 1 && arr[j] < arr[j + 1]) {//若存在两个子结点,则找到其中较大的子结点
            j++;
        }
        if (arr[j] > arr[k]) {//若k的较大子结点比k大,则交换它们的位置
            swap(arr, j, k);
            k = j;
        }
    }
}

堆排序

在介绍完堆的数据结构和操作方式后,我们来看堆排序是如何进行的。

堆的构造

  1. 将原数组看做堆的话,则最后一个分支结点(含有子结点的结点)在原数组中的索引为 (n-1)/2 -1
  2. 从(n-1)/2-1向前依次执行下沉操作,从而得到堆有序的数组
堆初始化

堆的排序

  1. 取出堆的根结点,与数组最后一个元素交换。交换后堆有序状态可能会被打破,需要在新的根结点进行下沉操作,使其恢复为堆有序状态。此时数组中最大(大顶堆)/最小(小顶堆)的值存放在数组末位,除它以外的最 大/小 值位于堆顶。
  2. 从数组中排除最后一个元素,重复步骤2,直到数组中的元素全部排除时,完成排序
堆排序
public static void heapSort(Integer arr[]) {
    int n = arr.length;
    //堆的构造,对每一个含有孩子的结点做下沉操作,得到大顶堆
    for (int i = (n-1) /2 -1; i >= 0; i--) {
        heapSink(arr, i, n);
    }
    printArr(arr, "大顶堆");
    for (int i = n - 1; i > 0; i--) {
        swap(arr, 0, i);
        heapSink(arr, 0, i);
    }
    printArr(arr, "堆排序");

}

public static void heapSink(Integer arr[], int i, int length) {
    while(2 * k + 1 <= length - 1) {//若k存在子结点,则进入循环
        int j = 2 * k + 1; //获取k的第一个子结点
        if (j < length - 1 && arr[j] < arr[j + 1]) {//若存在两个子结点,则找到其中较大的子结点
            j++;
        }
        if (arr[j] > arr[k]) {//若k的较大子结点比k大,则交换它们的位置
            swap(arr, j, k);
            k = j;
        }
    }
}

堆排序动态图


堆排序动态图

小结

堆排序算法也是一种选择排序算法,整体由堆的构建、堆的交换与下沉两个步骤组成。其中堆的构建需要比较(n-1)/2-1次下沉,每次下沉至多交换一次,时间复杂度为O(n);堆的交换与下沉中需交换n次,下沉依次需要执行\log_2(n-1),log_2(n-2)...1次交换,近似为Nlog_2N。因此堆排序的时间复杂度为O(N\log_2N)

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

推荐阅读更多精彩内容

  • 一些概念 数据结构就是研究数据的逻辑结构和物理结构以及它们之间相互关系,并对这种结构定义相应的运算,而且确保经过这...
    Winterfell_Z阅读 5,646评论 0 13
  • 1 初级排序算法 排序算法关注的主要是重新排列数组元素,其中每个元素都有一个主键。排序算法是将所有元素主键按某种方...
    深度沉迷学习阅读 1,389评论 0 1
  • 第一章 绪论 什么是数据结构? 数据结构的定义:数据结构是相互之间存在一种或多种特定关系的数据元素的集合。 第二章...
    SeanCheney阅读 5,735评论 0 19
  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,164评论 0 52
  • 概述:排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    每天刷两次牙阅读 3,726评论 0 15