二叉堆
一、堆的描述
习惯上,我们将二叉堆简称为“堆”,二叉堆是以数组存储的完全二叉树,是一种实现优先队列(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开始的数组中
按照这种存储方式,可知第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堆、左式堆、斜堆、二项堆、斐波那契堆等,但在信息学竞赛中极少触及,有兴趣可以适当了解,拓宽知识面,增强对“优先队列”的理解