c++ 二叉堆讲义(CMB整理)

二叉堆

一、堆的描述

习惯上,我们将二叉堆简称为“堆”,二叉堆是以数组存储的完全二叉树,是一种实现优先队列(priority queue)的数据结构

优先队列是至少允许插入(insert)和删除最小项(或最大项)(deleteMin or deleteMax)两种操作,有时我们可以添加一些其他的操作,但不属于优先队列基本模型的一部分

1、 堆的定义

n个元素称为堆,当且仅当它的关键字序列 ,满足序列从小到大的称为最小堆(也称小根堆),满足序列从大到小的称为最大堆(也称大根堆)

形象地说,父节点值大于或等于其孩子节点值的,叫最大堆;父节点值小于或等于孩子节点值的,叫最小堆
根据定义可知,堆是一颗完全二叉树。下图为树形结构表示的堆

在这里插入图片描述

2、 堆的性质

设树的高度为d,则堆具有如下的几个性质:
(1) 所有的叶结点不是处在第d层,就是处于第d-1层(完全二叉树性质)
(2) 当d≥1时,第d-1层上有 个结点(完全二叉树性质)
(3) 第d-1层上如果有分支结点,则这些分支结点都集中在树的最左边(完全二叉树性质)
(4) 每个结点所存放元素的关键字,都大于(最大堆)或小于(最小堆)它的子孙结点所存放元素的关键字(堆性质)

3、堆的存储特点

由于堆是一颗完全二叉树,根据完全二叉树的性质,可以用数组来存储堆

对于上图,将这两个堆保存在以1开始的数组中

位置:	1	2	3	4	5	6	7	8	9	10	11左图:	1	2	3	4	5	6	7	8	9	10	1右图:	11	9	10	5	6	7	8	1	2	3	4

按照这种存储方式,可知第i个元素的左右儿子分别是第2i和第2i+1个元素,而它的父亲节点是第 i/2 个元素

由于父亲节点和儿子节点具有这样的顺序关系,所以可以方便地由父亲节点找到儿子节点,反之亦然。可见,相对于链表存储方式,这种存储方式大大节省了存储空间,高效快速

在数组的存储方式下,堆有如下性质:
(1) 当用数组表示存储了n个元素的堆时,叶子结点的下标是
[n/2]+1,[n/2]+2,…,n
(2) 一个从小到大已排好序的数组是最小堆,反之则不然,因为堆的结构不唯一
(3) 一个最大堆(最小堆)中最小(最大)元素在堆的叶子结点上

定义一个堆:
int  heap[maxn] ;  //存储堆
int  len;        //堆中元素个数

二、堆的相关操作

堆作为一种优先队列的数据结构,其基本操作是:插入(入队)和删除最大项或最小项(优先级最低或最高的项出队)

下面是堆的一些常用操作 (heap[]为存放堆的数组,n为堆元素个数)。为了便于描述,假定这里的堆都是最小堆(最大堆的操作与最小堆类似)

将一个数插入堆:

在堆中插入元素x
首先将元素x放到堆中的最后一个位置(即最底层最右边的位置),然后不断地把x往上调整,直到x调不动为止(即大于它现在的父亲,或者x处于根结点)
在这里插入图片描述

代码实现:
void insert(int x) //将x放进堆 
{
	heap[++len]=x; //把当前数放进堆尾 
	int i=len; //i为要调整的数在heap[]中的位置 
	while (i>1&&heap[i]<heap[i/2]) 
	//还没调到堆头,因为是小根堆,要满足儿子大于父亲,如果儿子小于父亲说明需要调整 
	{
		swap(heap[i],heap[i/2]);//儿子和父亲交换 
		i/=2; //交换后要调整的数的位置也改变了 
	}
}
在堆中删除位置为k的元素

首先把位置k的元素和最后一个位置的元素交换,然后删去最后一个位置,这样k上的元素就被删除了
接着把位置k上的新元素不断下调,直到满足堆的性质
在这里插入图片描述

代码实现:
void del(int k) //删除堆中位置为k的元素
{
	int i=k,j; //i:父亲 j:儿子 
	heap[k]=heap[len];
	len--; //将堆顶赋值为堆尾的数,再删除堆尾,调整堆顶的位置(往下移动) 
	while (i*2<=len)//把还没调到堆尾 
	{
		if (i*2==len||heap[i*2]<heap[i*2+1]) j=i*2; else j=i*2+1;
		//小根堆要满足父亲小于儿子,左儿子小于右儿子,j就是找左右儿子较小的那一个,与父亲进行交换 
		if (heap[j]<heap[i]) swap(heap[j],heap[i]),i=j;
		//如果儿子小于父亲,可以交换,更新父亲的位置
		else return; //否则此时的二叉堆满足堆的性质,可以返回 
	}
}
建堆:
把元素不断插入到堆

在这里插入图片描述

代码实现:
void make_heap()
{
    n=0;
    for(int i=1;i<=m;i++)//m为要建堆的元素个数
    {  
       cin>>x
       insert(x);
    }
}

经典运用

1.堆排序
对原序列进行从小到大排序的方法如下:
(1)对原序列构造最大堆
(2)每次把堆顶heap[1](待排序元素的最大值)与最后一个元素heap[len]交换
(3)len–
(4)h[eap1]向下调整
(5)重复做(2)(3)(4)n-1遍,就排好

三、小结

堆是实现优先队列的一种数据结构,在插入、修改某元素值、删除最大项(或最小项)操作有着不俗的表现

时间复杂度均为:O(logn)

在竞赛中灵活使用最大堆和最小堆,对有频繁插入、删除、求最大(最小)值(或第k大值、第k小值)等试题,堆是一种非常不错的数据结构,同时也可以用最大堆和最小堆在空间上进行优化

优先队列还有一些其他的数据结构,如d堆、左式堆、斜堆、二项堆、斐波那契堆等,但在信息学竞赛中极少触及,有兴趣可以适当了解,拓宽知识面,增强对“优先队列”的理解

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值