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

前言

上一篇写了数据结构之二叉搜索树、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一次就好了。比每次都对开放列表进行排序的性能高的多。

参考

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

原文地址:https://www.cnblogs.com/lijiajia/p/10591554.html

时间: 2024-08-14 23:35:13

数据结构之二叉堆、堆排序的相关文章

D&amp;F学数据结构系列——二叉堆

二叉堆(binary heap) 二叉堆数据结构是一种数组对象,它可以被视为一棵完全二叉树.同二叉查找树一样,堆也有两个性质,即结构性和堆序性.对于数组中任意位置i上的元素,其左儿子在位置2i上,右儿子在左儿子后的单元2i+1中,它的父亲在[i/2](向下取整)中. 因此,一个数据结构将由一个数组.一个代表最大值的整数.以及当前的堆的大小组成.一个典型的优先队列(priority queue)如下: 1 #ifndef _BinHeap_H 2 struct HeapStruct; 3 type

数据结构 二叉堆 &amp; 堆排序

二叉堆,是一个满二叉树,满足堆的性质.即父节点大于等于子节点(max heap)或者是父节点小于等于子节点(min heap).二叉堆的如上性质常用于优先队列(priority queue)或是用于堆排序. 由于max heap 与min heap类似,下文只针对min heap进行讨论和实现. 如上图,是根据字母的ASCII码建立的最小堆. 我们用数组对满二叉树采用宽度优先遍历存储堆结构,如下图所示: 从数组下标1开始存储堆,这样的处理方式可以得到如下性质: 1.堆中的每个父节点k,他的两个子

【数据结构】二叉堆

看到一篇很好的博文,来自http://blog.csdn.net/morewindows/article/details/6709644 下面是博文内容 堆排序与快速排序,归并排序一样都是时间复杂度为O(N*logN)的几种常见排序方法.学习堆排序前,先讲解下什么是数据结构中的二叉堆. 二叉堆的定义 二叉堆是完全二叉树或者是近似完全二叉树. 二叉堆满足二个特性: 1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值. 2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆).

数据结构 之 二叉堆(Heap)

注:本节主要讨论最大堆(最小堆同理). 一.堆的概念 堆,又称二叉堆.同二叉查找树一样,堆也有两个性质,即结构性和堆序性. 1.结构性质: 堆是一棵被全然填满的二叉树.有可能的例外是在底层.底层上的元素从左到右填入.这种树称为全然二叉树(complete binary tree).下图就是这样一个样例. 对于全然二叉树,有这样一些性质: (1).一棵高h的全然二叉树,其包括2^h ~ (2^(h+1) - 1)个节点.也就是说.全然二叉树的高是[logN],显然它是O(logN). (2).全然

优先队列 - 数据结构 (二叉堆)

优先队列包括二叉堆.d-堆.左式堆.斜堆.二项队列等 1.二叉堆 堆是一棵被完全填满的二叉树,有可能例外的是在底层,底层上的元素从左到右填入.这样的树称为完全二叉树. 堆序的性质:在一个堆中,对于每一个节点X,X的父亲的关键字小于(或等于)X中的关键字,根节点除外(它没有父节点).完全二叉树可以用数组实现. //关于二叉堆的头文件定义 如果要插入的元素是新的最小值,那么它将一直被推向堆顶.这样在某一个时刻,i将是1,我们就需要另Insert函数令程序跳出while循环,这个值必须保证小于或者至少

数据结构之二叉堆(构建堆,堆排序)-(七)

/* * @author Lip * @date 2015-04-23 */ public class Heap { public static void main(String[] args) { // TODO Auto-generated method stub //System.out.println((int)-1.5); int []array={4,2,7,9,3,6,1,12,10,5}; System.out.println("原始:"); printHeapByLe

数据结构学习——二叉堆ADT(程序化)

参考书籍<数据结构与算法分析--C语言描述> 关于堆的一些基本概念,可参见小zz的另一篇博文. /*本例程实现的是最小堆,最大堆类似*/ #include<stdio.h> #include<stdlib.h> #include<stdbool.h> #include<string.h> #define MAX 13 typedef struct BHeap { int Capacity;//堆的最大容量 int Size;//当前堆大小 int

【笔记】【数据结构】二叉堆

作用: 插入元素,O(lgN) 修改元素,O(lgN) 删除元素,O(lgN) 查询元素,O(1) 动态查询最值,O(NlgN)-O(lgN)-O(1) 核心操作: 上浮与下沉 最小堆:上浮是指较小值上浮,下沉是指较大值下沉. 最大堆:上浮是指较大值上浮,下沉是指较小值下沉. 具体操作: 预处理中,对所有的根节点下沉操作,即交换根节点与一个较小的子节点,然后接着将子节点作为根节点,进行下一次下沉操作. 插入元素时,将它放入最底层,并不断地上浮. 删除堆顶元素时,将堆顶元素与最后一个元素交换,然后

数据结构之二叉搜索树、AVL自平衡树

前言 最近在帮公司校招~~ 所以来整理一些数据结构方面的知识,这些知识呢,光看一遍理解还是很浅的,看过跟动手做过一遍的同学还是很容易分辨的哟~ 一直觉得数据结构跟算法,就好比金庸小说里的<九阳神功>,学会九阳神功后,有了内功基础,再去学习其他武功,速度就有质的提升 内容大概包含这些,会分多篇文章来整理: 二叉搜索树 平衡二叉树(AVL) 二叉堆 堆排序 四叉树 八叉树 图,深度优先DFS.广度优先BFS 最短路径 二叉树 二叉树,也就是每个节点最多有两个孩子的树.多用于搜索,查找,还有可以用来