浅析基础数据结构-二叉堆

如题,二叉堆是一种基础数据结构

事实上支持的操作也是挺有限的(相对于其他数据结构而言),也就插入,查询,删除这一类

对了这篇文章中讲到的堆都是二叉堆,而不是斜堆,左偏树,斐波那契堆什么的 我都不会啊

一.堆的性质

1.堆是一颗完全二叉树

2.堆的顶端一定是“最大”,最小”的,但是要注意一个点,这里的大和小并不是传统意义下的大和小,它是相对于优先级而言的,当然你也可以把优先级定为传统意义下的大小,但一定要牢记这一点,初学者容易把堆的“大小”直接定义为传统意义下的大小,某些题就不是按数字的大小为优先级来进行堆的操作的

(但是为了讲解方便,下文直接把堆的优先级定为传统意义下的大小,所以上面跟没讲有什么区别?)

3.堆一般有两种样子,小根堆和大根堆,分别对应第二个性质中的“堆顶最大”“堆顶最小”,对于大根堆而言,任何一个非根节点,它的优先级都小于堆顶,对于小根堆而言,任何一个非根节点,它的优先级都大于堆顶(这里的根就是堆顶啦qwq)

来一张图了解一下堆(这里是小根堆)(原谅我丑陋无比的图)

不难看出,对于堆的每个子树,它同样也是一个堆(因为是完全二叉树嘛)

二.堆的操作

1.插入

假设你已经有一个堆了,就是上面那个

这个时候你如果想要给它加入一个节点怎么办,比如说0?

先插到堆底(严格意义上来说其实0是在5的左儿子的,图没画好放不下去,不过也不影响)

然后你会发现它比它的父亲小啊,那怎么办?不符合小根堆的性质了啊,那就交换一下他们的位置

交换之后还是发现不符合小根堆的性质,那么再换

还是不行,再换

好了,这下就符合小根堆的性质了,是不是顺眼很多了?(假的,图越来越丑,原谅我不想再画)

事实上堆的插入就是把新的元素放到堆底,然后检查它是否符合堆的性质,如果符合就丢在那里了,如果不符合,那就和它的父亲交换一下,一直交换交换交换,直到符合堆的性质,那么就插入完成了

Code:

void swap(int &x,int &y){int t=x;x=y;y=t;}//交换函数
int heap[N];//定义一个数组来存堆
int siz;//堆的大小
void push(int x){//要插入的数
    heap[++siz]=x;
    now=siz;
    //插入到堆底
    while(now){//还没到根节点,还能交换
        ll nxt=now>>1;//找到它的父亲
        if(heap[nxt]>heap[now])swap(heap[nxt],heap[now]);//父亲比它大,那就交换
        else break;//如果比它父亲小,那就代表着插入完成了
        now=nxt;//交换
    }
    return;
}

2.删除

把0插入完以后,忽然你看这个0不爽了,本来都是正整数,怎么就混进来你这个0?

于是这时候你就想把它删除掉

怎么删除?在删除的过程中还是要维护小根堆的性质

如果你直接删掉了,那就没有堆顶了,这个堆就直接乱了,所以我们要保证删除后这一整个堆还是个完好的小根堆

首先在它的两个儿子里面,找一个比较小的,和它交换一下,但是还是没法删除,因为下方还有节点,那就继续交换

还是不行,再换

再换

好了,这个碍眼的东西终于的下面终于没有节点了,这时候直接把它扔掉就好了

这样我们就完成了删除操作,但是在实际的代码操作中,并不是这样进行删除操作的,有一定的微调,代码中是直接把堆顶和堆底交换一下,然后把交换后的堆顶不断与它的子节点交换,直到这个堆重新符合堆性质(但是上面的方式好理解啊)

手写堆的删除支持任意一个节点的删除,不过STL只支持堆顶删除,STL的我们后面再讲

Code:

void pop(){
    swap(heap[siz],heap[1]);siz--;//交换堆顶和堆底,然后直接弹掉堆底
    int now=1;
    while((now<<1)<=siz){//对该节点进行向下交换的操作
        int nxt=now<<1;//找出当前节点的左儿子
        if(nxt+1<=siz&&heap[nxt+1]<heap[nxt])nxt++;//看看是要左儿子还是右儿子跟它换
        if(heap[nxt]<heap[now])swap(heap[now],heap[nxt]);//如果不符合堆性质就换
        else break;//否则就完成了
        now=nxt;//往下一层继续向下交换
    }
}

3.查询

因为我们一直维护着这个堆使它满足堆性质,而堆最简单的查询就是查询优先级最低/最高的元素,对于我们维护的这个堆heap,它的优先级最低/最高的元素就是堆顶,所以查询之后输出heap[1]就好了

一般的题目里面查询操作是和删除操作捆绑的,查询完后顺便就删掉了,这个主要因题而异

三.堆的STL实现

这年头真的没几个人写手写堆(可能有情怀党?)

一是手写堆容易写错代码又多,二是STL 直接给我们提供了一个实现堆的简单方式:优先队列

手写堆和STL的优先队列有什么 区别?没有区别

速度方面,手写堆会偏快一点,但是如果开了O2优化优先队列可能会更快;

代码实现难度方面:优先队列完爆手写堆

这两方面综合起来,一般都是用STL的优先队列来实现堆,省选开O2啊

至于为什么前面讲堆的操作时用手写堆,好理解嘛,最好先根据上面的代码和图理解一下堆是怎么实现那些操作的,再来看一下下面的STL的操作

定义一个优先队列:

首先你需要一个头文件:#include<queue>
priority_queue<int> q;//这是一个大根堆q
priority_queue<int,vector<int>,greater<int> >q;//这是一个小根堆q
//注意某些编译器在定义一个小根堆的时候greater<int>和后面的>要隔一个空格,不然会被编译器识别成位运算符号>>

优先队列的操作:

q.top()//取得堆顶元素,并不会弹出
q.pop()//弹出堆顶元素
q.push()//往堆里面插入一个元素
q.empty()//查询堆是否为空,为空则返回1否则返回0
q.size()//查询堆内元素数量

常用也就这些,貌似还有其他,不过基本也用不到,知道上面那几个也就可以了

不过有个小问题就是STL只支持删除堆顶,而不支持删除其他元素

但是问题不大,开一个数组del,在要删除其他元素的时候直接就标记一下del[i]=1,这里的下标是元素的值,然后在查询的时候碰到这个元素被标记了直接弹出然后继续查询就可以了 (前两天刚从学长处get这个姿势)

另外因为STL好写,下面堆的应用全部都会采用STL的代码实现(懒啊,如果有放代码的话)

四.堆的复杂度

因为堆是一棵完全二叉树,所以对于一个节点数为n的堆,它的高度不会超过log2n

所以对于插入,删除操作复杂度为O(log2n)

查询堆顶操作的复杂度为O(1)

五.堆的应用

1.堆排序

其实就是用要排序的元素建一个堆(视情况而定是大根堆还是小根堆),然后依次弹出堆顶元素,最后得到的就是排序后的结果了

但是裸的并没有什么用,我们有sort而且sort还比堆排快,所以堆排一般都没有这种模板题,一般是利用堆排的思想,然后来搞一些奇奇怪怪的操作,第2个应用就有涉及到一点堆排的思想

2.用两个堆来维护一些查询第k小/大的操作

洛谷P1801 黑匣子

利用一个大根堆一个小根堆来维护第k小,并没有强制在线

不强制在线,所以我们直接读入所有元素,枚举询问,因为要询问第k小,所以把前面的第k个元素都放进大根堆里面,然后如果元素数量大于k,就把堆顶弹掉放到小根堆里面,使大根堆的元素严格等于k,这样这次询问的结果就是小根堆的堆顶了(前面k-1小的元素都在大根堆里面了)

记得在完成这次询问后重新把小根堆的堆顶放到大根堆里面就好

为了不占太多篇幅就不粘Code了

其他应用之后再补

原文地址:https://www.cnblogs.com/henry-1202/p/9307927.html

时间: 2024-08-04 14:01:54

浅析基础数据结构-二叉堆的相关文章

基本数据结构——二叉堆

迅速补档,为A*做一下铺垫… 概念定义 二叉堆就是一个支持插入.删除.查询最值的数据结构.他其实是一棵完全二叉树.那么堆一般分为大根堆和小根堆 大根堆 树中的任意一个节点的权值都小于或者等于其父节点的权值,则称该二叉树满足大根堆性质. 小根堆 树中的任意一个节点的权值都大于或者等于其父节点的权值,则称该二叉树满足小根堆性质. 习惯用法 一般习惯把堆用数组保存.才用父子二倍的编号方式.即:对于某一个节点x,其左儿子节点为2*x,右儿子节点为x*2+1 支持功能及代码实现 Insert插入 向二叉堆

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

数据结构--二叉堆与堆排序

二叉堆的概念 二叉堆,BinaryHeap,是二叉树中的常见的一种结构.通常以最大堆和最小堆的形式呈现.最大堆指的是父节点大于等于孩子节点的value值,也就是说对于最大堆而言,根元素是二叉堆最大的元素.最小堆的概念是与最大堆的概念是相似的.下图是最大堆的示意图: 二叉堆和排序之间的联系 二叉堆最显著的特征就是根元素是二叉树元素间最大的或者最小的.因此每次将二叉树最大或者最小的元素取出来,同时保证每次进行这样的操作后,剩下的元素依然可以保持二叉堆的性质,这样迭代这个过程,就可以完成排序的目的.

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

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

算法—二叉堆

实现栈或是队列与实现优先队列的最大不同在于对性能的要求.对于栈和队列,我们的实现能够在常数时间内完成所有操作:而对于优先队列,插入元素和删除最大元素这两个操作之一在最坏情况下需要线性时间来完成.我们接下来要讨论的基于数据结构堆的实现能够保证这两种操作都能更快地执行. 1.堆的定义 数据结构二叉堆能够很好地实现优先队列的基本操作.在二叉堆的数组中,每个元素都要保证大于等于另两个特定位置的元素.相应地,这些位置的元素又至少要大于等于数组中的另两个元素,以此类推.如果我们将所有元素画成一棵二叉树,将每

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.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆).

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

2014.06.15 22:14 简介: 堆是一种非常实用的数据结构,其中以二叉堆最为常用.二叉堆可以看作一棵完全二叉树,每个节点的键值都大于(小于)其子节点,但左右孩子之间不需要有序.我们关心的通常只有堆顶的元素,而整个堆则被封装起来,保存在一个数组中. 图示: 下图是一个最大堆: 实现: 优先队列是STL中最常用的工具之一,许多算法的优化都要利用堆,使用的工具就是优先队列.STL中的优先队列通过仿函数来定义比较算法,此处我偷懒用了“<”运算符.关于使用仿函数的好处,我之后如果有时间深入学习S