注:本节主要讨论最大堆(最小堆同理)。

一、堆的概念
    堆,又称二叉堆。同二叉查找树一样,堆也有两个性质,即结构性和堆序性。
    1、结构性质:
    堆是一棵被完全填满的二叉树,有可能的例外是在底层,底层上的元素从左到右填入。这样的树称为完全二叉树(complete binary tree)。下图就是这样一个例子。
    
    对于完全二叉树,有这样一些性质:
    (1)、一棵高h的完全二叉树,其包含2^h ~ (2^(h+1) - 1)个节点。也就是说,完全二叉树的高是[logN],显然它是O(logN)。
    (2)、完全二叉树可以用数组进行结构表示:

index

0

1

2

3

4

5

6

7

8

9

10

11

12

13

value


A

B

C

D

E

F

G

H

I

J





    仔细考察该数组的index和元素在树中的分布情况,可以得到:
    对于一个三元素的二叉树,树结构和数组索引有如下关系:
    leftChild.index = 2 * parent.index;
    rightChild.index = 2 * parent.index + 1; 
    (3)、通过前面的讨论,我们可以这样去看待一个堆的数据结构:
    一个数组、当前堆的大小heapLen。
    2、堆序性质:
    使操作被快速执行的性质是堆序性(heap order)。
    堆序性质:在一个堆中,对于每一个节点x,x的父亲中的关键字大于(或等于)x中的关键字,根节点除外(它没有父节点)。
    根据堆序性质,最大元总可以在根处找到。因此,我们以常数时间完成查找操作。
    比较:
    堆序性质的堆:
    
    无堆序性质的堆:
    

二、基本堆操作

    声明:
    int heap[MAX+1];
    int heapLen; //堆的大小

    int leftEle(int i){ return i*2; }
    int rightEle(int i){ return i*2+1; }
    int parentEle(int i){ return i/2; }
    void swap(int i, int j){
        int tmp;
        tmp = i, i = j, j = tmp;
    }


    1、查询操作:    

    int findMax()
    {
        return heap[1];
    }

    函数解析:
    堆的最大值即为根节点元素,直接返回该值即可。


    2、堆维护操作:

    下沉操作:
    void maxHeapify(int i)
    {
        int iLeft = leftEle(i);    //找到该节点的左儿子
        int iRight = rightEle(i);    //找到该节点的右儿子
        int largest = i;    //记录最大值节点,初始为节点自己
        
        //找到最大值对应的节点
        if( iLeft < heapLen && heap[i] < heap[iLeft] )
            largest = iLeft;
        if(iRight < heapLen && heap[largest] < heap[iRight] )
            largest = iRight;
        
        //交换原节点与最大值对应的节点,然后对交换后的节点进行堆维护操作
        if(largest != i)
        {
            swap(heap[i], heap[largest]);
            maxHeapify(largest);
        }
    }


    3、建堆操作:    

    在给出具体如何建堆的操作之前,我们可以考察一下具体应该怎样去实现。
    现在给出一个堆(应该不能称之为堆),这个堆由初始数组构造而成,其结构为:
    
    显然这不是最大堆。
    整个数组为:    
index
83
11
6
15
36
19
value
1
2
3
4
5
6
    经过一系列的操作,我们需要将该堆转换为:
    
    整个最大堆化过程是这样的:自下而上逐层维护堆操作。
    首先,找到第一个有子树的节点,对该节点进行堆维护操作,然后依次向上,进行堆维护。

    这里的问题:
    第一个有子树的节点在哪里?
    ===>>>>>
    对于完全二叉树,叶子节点必然存放在数组的尾端,现在的问题就在于叶子节点到底有多少个?知晓叶子节点的个数后,就可以很容易地确定有子树节点的位置。那么叶子节点到底有多少个呢?
    设完全二叉树总共有n个节点,叶子节点有n0个,由于二叉树的节点的度数最大为2,于是可设度数为1的节点数为n1,度数为2的节点数为n2。
    于是我们可以得到这样几个关系式:
    n0+n1+n2 = n;
    n-1 = 2*n2 + n1;(边数的两种不同表示方式)
    解此方程式,可以得到:    
    n0 = (n+1-n1)/2.
    对于完全二叉树,n1 = 1或0
    当n1=1时,n0=n/2;当n1=0时,n0=(n+1)/2。
    于是我们可以得到叶子节点为总节点数的一半。
    从而有,非叶子节点应该是数组的前半部分。

    ===>>>
    void buildHeap()
    {    
        int i;
        for( i = heapLen/2; i > 0; i--)
            maxHeapify(i);
    }


    4、排序操作:    

    堆排序的关键在于将最大值元素交换到数组尾端,重新进行堆维护操作。依次循环操作,即可以得到排序的数组。
    void heapSort()
    {
        int i;
        buileHeap();
        for( i=heapLen; i>=1; i--)
        {
            swap(heap[heapLen], heap[1]);
            heapLen--;
            maxHeapify(1);
        }
    }
    
    函数解析:
    首先我们先利用堆排序对一数组中的元素进行排序:
23
1
16
9
54

    现在进行堆排序:
    a、建堆:
    
    b、交换54和1,并解除堆最后一个元素与原堆的关系:
    
    c、重构堆:
    
    d、依次循环最终得到:
        
    这样,数组变为:
1
9
16
23
54

从而完成了对数组的排序。


    5、插入元素操作:    

    插入insertHeap():该操作同优先队列(priority queue)中的push操作。
    在介绍具体的插入操作前,需要实现increaseKey(int i, int key)函数,用于更新堆结构。
    上浮操作:
    void increaseKey(int i, int key)
    {
        assert(key >= heap[i]);    //断言key值大于heap[i],如果不成立,则终止并报错
        heap[i] = key;
        while(i > 1 && heap[parentEle(i)] < heap[i])
        {
            swap(heap[i], heap[parentEle(i)]);
            i = parentEle(i);
        }
    }
    在这里,需要着重介绍一下increaseKey操作的具体步骤,举例说明:
    对于这样一个堆,将节点6的值由8增加到54—>>>:
    
    整个操作过程即为increaseKey(6, 54)。
    整个过程如下:
    
    于是,插入元素到堆的代码如下:
    void insertHeap( int x )
    {
        heapLen++;
        heap[heapLen] = -INF;
        increaseKey(heapLen, x);
    }

    6、删除元素操作:

    删除deleteHeapMax():相当于优先队列中的pop()操作。
    int deleteHeapMax()
    {
        int ret = heap[1];
        swap(ret, heap[heapLen]);
        heapLen--;
        maxHeapify(1);
        return ret;
    }


三、算法分析:
查询操作
O(1)
堆维护操作
O(logN)
建堆操作
O(NlogN)
堆排序操作
O(NlogN)

数据结构 之 二叉堆(Heap)的更多相关文章

  1. D&F学数据结构系列——二叉堆

    二叉堆(binary heap) 二叉堆数据结构是一种数组对象,它可以被视为一棵完全二叉树.同二叉查找树一样,堆也有两个性质,即结构性和堆序性.对于数组中任意位置i上的元素,其左儿子在位置2i上,右儿 ...

  2. 【算法与数据结构】二叉堆和优先队列 Priority Queue

    优先队列的特点 普通队列遵守先进先出(FIFO)的规则,而优先队列虽然也叫队列,规则有所不同: 最大优先队列:优先级最高的元素先出队 最小优先队列:优先级最低的元素先出队 优先队列可以用下面几种数据结 ...

  3. 【数据结构与算法Python版学习笔记】树——利用二叉堆实现优先级队列

    概念 队列有一个重要的变体,叫作优先级队列. 和队列一样,优先级队列从头部移除元素,不过元素的逻辑顺序是由优先级决定的. 优先级最高的元素在最前,优先级最低的元素在最后. 实现优先级队列的经典方法是使 ...

  4. 数据结构图文解析之:二叉堆详解及C++模板实现

    0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...

  5. POJ 2010 - Moo University - Financial Aid 初探数据结构 二叉堆

    考虑到数据结构短板严重,从计算几何换换口味= = 二叉堆 简介 堆总保持每个节点小于(大于)父亲节点.这样的堆被称作大根堆(小根堆). 顾名思义,大根堆的数根是堆内的最大元素. 堆的意义在于能快速O( ...

  6. 二叉堆(binary heap)

    堆(heap) 亦被称为:优先队列(priority queue),是计算机科学中一类特殊的数据结构的统称.堆通常是一个可以被看做一棵树的数组对象.在队列中,调度程序反复提取队列中第一个作业并运行,因 ...

  7. 《数据结构与算法分析:C语言描述》复习——第五章“堆”——二叉堆

    2014.06.15 22:14 简介: 堆是一种非常实用的数据结构,其中以二叉堆最为常用.二叉堆可以看作一棵完全二叉树,每个节点的键值都大于(小于)其子节点,但左右孩子之间不需要有序.我们关心的通常 ...

  8. Binary Heap(二叉堆) - 堆排序

    这篇的主题主要是Heapsort(堆排序),下一篇ADT数据结构随笔再谈谈 - 优先队列(堆). 首先,我们先来了解一点与堆相关的东西.堆可以实现优先队列(Priority Queue),看到队列,我 ...

  9. 堆(Heap)和二叉堆(Binary heap)

    堆(Heap) The operations commonly performed with a heap are: create-heap: create an empty heap heapify ...

随机推荐

  1. AngularJS进阶(十九)在AngularJS应用中集成百度地图实现定位功能

    在AngularJS应用中集成百度地图实现定位功能 注:请点击此处进行充电! 前言 根据项目需求,需要实现手机定位功能,考虑到百度业务的强大能力,遂决定使用百度地图第三方服务. 添加第三方模块的步骤与 ...

  2. Linux 套接字编程中的 5 个隐患(转)

    本文转自IBM博文Linux 套接字编程中的 5 个隐患. “在异构环境中开发可靠的网络应用程序”. Socket API 是网络应用程序开发中实际应用的标准 API.尽管该 API 简单,但是开发新 ...

  3. Android群英传笔记——摘要,概述,新的出发点,温故而知新,可以为师矣!

    Android群英传笔记--摘要,概述,新的出发点,温故而知新,可以为师矣! 当工作的越久,就越感到力不从心了,基础和理解才是最重要的,所以买了两本书,医生的<Android群英传>和主席 ...

  4. Machine Learning 学习笔记

    点击标题可转到相关博客. 博客专栏:机器学习 PDF 文档下载地址:Machine Learning 学习笔记 机器学习 scikit-learn 图谱 人脸表情识别常用的几个数据库 机器学习 F1- ...

  5. C语言在linux内核中do while(0)妙用之法

    为什么说do while(0) 妙?因为它的确就是妙,而且在linux内核中实现是相当的妙,我们来看看内核中的相关代码: #define db_error(fmt, ...) \ do { \ fpr ...

  6. SharePoint 2010 之soap:Server服务器无法处理请求

    算是一个下马威?!刚刚部署上的SharePoint2010环境,感觉很欣喜,开始操作,结果装上Designer2010,打开页面,居然报错 错误内容:soap:Server服务器无法处理请求. --- ...

  7. 解决Cell重用内容混乱的几种简单方法,有些方法会增加内存

    重用实现分析 查看UITableView头文件,会找到NSMutableArray*  visiableCells,和NSMutableDictnery* reusableTableCells两个结构 ...

  8. 面试之路(6)-BAT面试之操作系统内存详解

    本文主要参考两篇博客,读后整理出来,以供大家阅读,链接如下: http://blog.jobbole.com/95499/?hmsr=toutiao.io&utm_medium=toutiao ...

  9. Java IO学习--(一)概述

    在这一小节,我会试着给出Java IO(java.io)包下所有类的概述.更具体地说,我会根据类的用途对类进行分组.这个分组将会使你在未来的工作中,进行类的用途判定时,或者是为某个特定用途选择类时变得 ...

  10. JQuery(一)---- JQ的选择器,属性,节点,样式,函数等操作详解

    JQuery的基本概念 JQuery是一个javascript库,JQuery凭借着简洁的语法和跨平台的兼容性,极大的简化了js操作DOM.处理事件.执行动画等操作.JQuery强调的理念是:'wri ...