原理
堆是一颗完全二叉树,其中堆顶一定是“最大”或“最小”的,这里的“最大”“最小”是相对于优先级而言,并不是简单的数值大小。堆一般分为小根堆和大根堆,即对于任一非根节点,它的优先级都大于或小于堆顶。
其中,C++的STL中的优先队列就是二叉堆的应用。
堆的操作
1.插入
对于堆的插入,实现原理就是不断与其父结点进行比较,如果不满足大根堆或小根堆的性质,则进行交换,由于堆是完全二叉树,所以满足关系式——父结点下标 = 左子树下标 ÷ 2 = (右子树下标 - 1) ÷ 2,左子树下标 = 父结点下标 × 2,右子树下标 = 父结点下标 × 2 + 1。
2.删除
对于堆的删除,实现原理是将堆顶与堆底进行交换,然后交换后的堆顶不断与其子节点进行比较交换,直到重新符合堆的性质。
//以小根堆为例
void push(int x)
{
heap[++siz] = x; //siz表示堆的大小
int cur = siz;
while (cur) //还未到根节点,继续比较看是否需要交换
{
int nextPos = (cur >> 1);
if (heap[nextPos] > heap[cur])
{
Swap(heap[nextPos, heap[cur]);
}
else
{
break;
}
cur = nextPos;
}
}
void pop()
{
Swap(heap[siz], heap[1]); //交换堆底和堆顶,弹出堆底
siz--;
int cur = 1;
while ((cur << 1) <= siz) //重新调整堆
{
int nextPos = (cur << 1);
if (nextPos + 1 <= siz && heap[nextPos + 1] < heap[nextPos])
{
nextPos++;
}
if (heap[nextPos] < heap[cur])
{
Swap(heap[nextPos], heap[cur]);
}
else
{
break;
}
cur = nextPos;
}
}
两个操作的时间复杂度都为 O ( l o g 2 n ) O(log_2\ n) O(log2 n)。
STL中的priority_queue
C++的标准模板库中提供了许多实用的函数和数据结构,其中优先队列就利用了二叉堆的结构特点,使用方式如下:
#include<iostream>
using namespace std;
#include<queue>
priority_queue<Type> q; //也可写作priority_queue<Type, vector<Type>, less<Type> > q,表示大根堆
priority_queue<Type, vector<Type>, greater<Type> > q; //表示小根堆
//注意greater<Type>和结尾的>中间的空格,某些编译器会将其编译成位运算符>>
优先队列封装的常用函数有如下几个:
q.top(); //取堆顶,但不弹出
q.pop(); //弹出堆顶
q.push(x); //向堆中插入一个元素
q.empty(); //判断堆是否为空,空返回1非空返回0
q.size(); //返回堆的大小
而一般在使用优先队列时,往往对象并非是一个简单的数据类型。例如将结构体作为对象时,在建堆过程中,此时前言提到的优先级问题就有了重要的意义,往往结构体中变量是不唯一的,那么在建堆时如何根据所需来建一个合理的堆呢,此时就需要重载运算符:
struct node
{
int val, rnd;
bool operator < (const node& x)const
{
return rnd < x.rnd;
}
}a[maxn];
通过重载运算符 < 使其表示为rnd的优先级,如此一来就能够完成堆的一系列操作。