数据结构 之 二叉堆(Heap)

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

一、堆的概念

堆,又称二叉堆。同二叉查找树一样,堆也有两个性质,即结构性和堆序性。

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的父亲中的keyword大于(或等于)x中的keyword,根节点除外(它没有父节点)。

依据堆序性质。最大元总能够在根处找到。

因此,我们以常数时间完毕查找操作。

比較:

堆序性质的堆:

无堆序性质的堆:

二、基本堆操作


    声明:

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

经过一系列的操作,我们须要将该堆转换为:

整个最大堆化过程是这种:自下而上逐层维护堆操作。

首先,找到第一个有子树的节点。对该节点进行堆维护操作,然后依次向上,进行堆维护。



    这里的问题:

第一个有子树的节点在哪里?

===>>>>>

对于全然二叉树,叶子节点必定存放在数组的尾端,如今的问题就在于叶子节点究竟有多少个?知晓叶子节点的个数后,就能够非常easy地确定有子树节点的位置。

那么叶子节点究竟有多少个呢?

设全然二叉树总共同拥有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)
时间: 2024-12-06 09:45:39

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

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

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

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

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

【数据结构】二叉堆

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

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

前言 上一篇写了数据结构之二叉搜索树.AVL自平衡树,这次来写堆. 堆的创造者 很久以前排序算法的时间复杂度一直是O(n^2), 当时学术界充斥着"排序算法不可能突破O(n^2)"的声音,直到1959年,由D.L.Shell提出了一种排序算法,希尔排序(Shell Sort),才打破了这种不可能的声音,把排序算法的时间复杂度提升到了O(n^3/2)! 当科学家们知道这种"不可能"被突破之后,又相继有了更快的排序算法,"不可能超越O(n^2)"彻底

数据结构学习——二叉堆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

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

/* * @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

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

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

二叉堆(binary heap)

堆(heap) 亦被称为:优先队列(priority queue),是计算机科学中一类特殊的数据结构的统称.堆通常是一个可以被看做一棵树的数组对象.在队列中,调度程序反复提取队列中第一个作业并运行,因而实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些不短小,但具有重要性的作业,同样应当具有优先权.堆即为解决此类问题设计的一种数据结构. 本文地址:http://www.cnblogs.com/archimedes/p/binary-heap.html,转载请注明源地址. 逻辑定义 n个

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

考虑到数据结构短板严重,从计算几何换换口味= = 二叉堆 简介 堆总保持每个节点小于(大于)父亲节点.这样的堆被称作大根堆(小根堆). 顾名思义,大根堆的数根是堆内的最大元素. 堆的意义在于能快速O(1)找到最大/最小值,并能持续维护. 复杂度 push() = O(logn); pop() = O(logn); BinaryHeap() = O(nlogn); 实现 数组下标从1开始的情况下,有 Parent(i) = i >> 1 LChild(i) = i << 1 RChi