数据结构之二叉堆、堆排序

前言

上一篇写了数据结构之二叉搜索树、AVL自平衡树,这次来写堆。

堆的创造者

很久以前排序算法的时间复杂度一直是O(n^2), 当时学术界充斥着“排序算法不可能突破O(n^2)”的声音,直到1959年,由D.L.Shell提出了一种排序算法,希尔排序(Shell Sort),才打破了这种不可能的声音,把排序算法的时间复杂度提升到了O(n^3/2)!

当科学家们知道这种"不可能"被突破之后,又相继有了更快的排序算法,“不可能超越O(n^2)”彻底成为了历史。

在1964年,没错,是55年前!堆排序这种奇思妙想的,十分精彩的,排序算法诞生了!时间复杂度为O(nlogn),远甩O(n^2)

由Robert W. Floyd(罗伯特·弗洛伊德)和J.W.J. Williams(威廉姆斯)共同发明了著名的堆排序,同时也发明了“堆”这样的数据结构, Floyd在1978年获得了图灵奖!真是个狼人!!(比很人还要多一点)

有时候了解下历史,也是十分有趣的!虽然你可能会觉得并没什么卵用~

堆是什么?

之前第一次听到这个词的时候,感觉像是一堆乱七八糟的东西,完全跟树连想不到一起,后来才知道,原来也是一颗二叉树,而且是完全二叉树

堆的性质:

堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。

大顶堆
小顶堆

如何用数组表示堆?

我们可以把堆,存放在一个数组中,根据索引来获取节点,那么如何通过索引表示父子关系呢?
堆是一颗完全二叉树,所以满足如下条件

假如当前的节点索引为:k
父节点索引:(k-1) / 2
左孩子节点:2 * k + 1
右孩子节点:2 * k + 2

根据这个规律,我们就可以用索引来计算出父子节点的位置了。这样就能把堆存放在数组中使用,会更加节省内存。

堆排序算法

堆排序算法就是形成一个堆后,假如是大顶堆,堆顶肯定是最大的元素,那我们每次都把堆顶的最大元素拿走,然后把堆末尾的元素放到堆顶来,但是这个元素不一定是当前最大的,所以还要对这个元素在堆里进行比较,把最大的元素放到堆顶,再取出来。如此我们每次取出的都是剩余元素中最大的元素,就能得到一组从大到小有序的元素。下面我们来用大顶堆对一组数据进行堆排序计算。

数据为:[50, 10, 90, 30, 70, 40, 80, 60, 20]

算法分为两个部分

1.如何将一组无序的数据构建出一个初始的大顶堆?
2.在拿走堆顶元素之后,如何计算出新的堆顶元素?

首先我们要实现一个操作:如果一个节点的子节点比它更大,就交换位置,如果子节点还有子节点,就要继续比下去,直到末尾。这个操作我们称为:HeapOne

    public void HeapOne(List<int> list, int len, int s)
    {
        int temp, j;
        
        temp = list[s];//先把指定要下沉节点的值取出来
        
        for (j = (2 * s)+1; j < len; j = (j*2)+1)
        {
            if (j < (len - 1) && list[j] < list[j + 1])//看看左右两个子节点谁更大,就取谁
                ++j;
            
            if (temp >= list[j])//子节点比父节点小,就不管
                break;

            list[s] = list[j];//先把子节点的值给父节点
            s = j;//继续从这个子节点往下比较下去
        }
        list[s] = temp;
    }

实现这个操作之后,就可以开始我们的第一个部分了,形成初始大顶堆。

从最后一个非叶子节点开始,对该节点进行HeapOne,一直从下往上,直到把所有的父节点都HeapOne了一遍,一个初始的大顶堆就形成了。

    public void HeapSort(List<int> list)
    {
        int i;
        for (i = (list.Count - 1) / 2; i >= 0; i--)//第一部分,形成一个初始大顶堆
        {
            HeapOne(list, list.Count, i);
        }

        for (i = list.Count -1; i > 0; i--)//每拿走一个元素,都重新计算新堆
        {
            int temp = list[0];
            list[0] = list[i];
            list[i] = temp;
            
            HeapOne(list, i, 0);
        }
    }

算法第二部分

  1. 我们把堆顶的元素取出,放到一个临时变量里存着。
  2. 然后把堆的最末尾元素取出来,放到堆顶。
  3. 把堆的长度-1(因为已经取出之前的堆顶元素了)
  4. 接着对刚刚这个从末尾放到堆顶的元素,进行HeapOne操作,让他跟子节点比较,把最大的元素交换到堆顶来,再次形成最大堆。

一直重复这个操作后,直到最后一个堆顶被取出,放到数组末尾,堆的长度也就为0了,我们的数组也就形成了一组从大到小的数列。

如此,堆排序就完成了

总结

堆排序性能比较稳定,时间复杂度包含初始堆+排序时重建堆为:O(nlogn)。
在游戏开发中也会经常使用到堆

  1. 比如Top K问题,从n个数据中,找出最大的前100个。
  2. 用堆来实现优先加载队列。
  3. A星寻路算法中,可以用最小堆来对寻路的开放列表维护顺序,把f值最小的放在堆顶,每次取出堆顶后,再HeapOne一次就好了。比每次都对开放列表进行排序的性能高的多。

参考

百度百科-堆排序
《大话数据结构》-程杰

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

推荐阅读更多精彩内容