堆的定义
堆结构是一种数组对象,可以被视为一颗完全二叉树。
树中的每个节点与数组中存放该节点中值的那个元素相对应。
由上图不难看出
当然也可以用位运算来加快速度
由于i*2一定为偶数(i∈N+),所以i<<1|1等价于i*2+1.
堆的性质
n个关键字序列L[1…n]称为堆,当且仅当该序列满足:
1. L(i)<=L(2i)且L(i)<=L(2i+1)或
2. L(i)>=L(2i)且L(i)>=L(2i+1)
满足第一个条件的成为小根堆(即每个结点值小于它的左右孩子结点值),满足第二个添加的成为大根堆(即每个结点值大于它的左右孩子结点值)。
堆的操作
1.向堆中加入一个元素(push),并保持堆的有序
2.从队中删除一个元素(pop),并保持堆的有序
3.查找最小/最大值(top).
算法分析
插入一个元素:
1.在堆尾加入一个元素,把这个节点设置为当前结点
2.维护——
比较当前结点和它的父结点的大小,小于父结点的话就交换它们的值,一直交换直到当前结点大于等于它的父结点(小根堆),大根堆相反。删除一个元素
1.把堆的最后一个结点(heap[size])放到根的位置上(将根结点覆盖),pa指向根结点,size–。
2.根结点没有儿子就return;否则,取儿子中最小的一个与其比较,比子结点大就交换它们的值,pa指向子结点。(小根堆,大根堆则是取儿子中最大的比较,比子结点小就交换)查找最小、最大值
就是堆顶元素
具体实现
插入操作
void push(int d)//向堆heap中插入d
{
int now,next;
heap[++size]=d;
now=size;
while(now>1)
{
next=now>>1;
if(heap[now]>=heap[next]) return;//小根堆
swap(heap[now],heap[next]);
now=next;
}
}
//或者是使用stl里面堆的维护操作:
void put(int d)
{
heap[++size]=d;
push_heap(heap+1,heap+1+size,greater()<int>);//小根堆
//push_heap(heap+1,heap+1+size); 默认是大根堆
}
删除操作
void pop()
{
int now=1,next;
heap[1]=heap[size--];
while((now<<1)<=size)
{
next=now<<1;
if(next<size&&heap[next+1]<heap[next]) next++;
if(heap[now]<=heap[next]) break;
swap(heap[now],heap[next]);
now=next;
}
}
//使用stl:
void pop(int d)
{
pop_heap(heap+1,heap+1+size,greater()<int>);
//pop_heap(heap+1,heap+1+size); 这是大根堆
}
查找
int get()
{
return heap[1];
}
当然以上操作也可以使用优先队列容器(priority_queue)
洛谷上关于小根堆的测试https://www.luogu.org/problemnew/show/3378
手写堆:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1000000+121;
int size,n,heap[N];
void swap(int &a,int &b)
{
int t=a;
a=b,b=t;
}
void put(int d)
{
int now,next;
heap[++size]=d;
now=size;
while(now>1)
{
next=now>>1;
if(heap[now]>=heap[next]) return;
swap(heap[now],heap[next]);
now=next;
}
}
void pop()
{
int now=1,next;
heap[1]=heap[size--];
while((now<<1)<=size)
{
next=now<<1;
if(next<size&&heap[next+1]<heap[next]) next++;
if(heap[now]<=heap[next]) break;
swap(heap[now],heap[next]);
now=next;
}
}
int get()
{
return heap[1];
}
int main()
{
int a,b;
scanf("%d",&n);
while(n--)
{
scanf("%d",&a);
if(a==1)
{
scanf("%d",&b);
put(b);
}
else if(a==2)
printf("%d\n",get());
else pop();
}
}
优先队列容器:
#include<iostream>
#include<cstdio>
#include<queue>//要用stl的队列或者优先队列的话不要忘了这个头文件
#include<algorithm>
using namespace std;
//定义一个优先队列h,greater<>表示为小根堆
//vector<>是向量,动态数组的一种,所占内存随插入元素的增加而增加
priority_queue<int,vector<int>,greater<int> >h;
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int a;
scanf("%d",&a);
if(a==1)
{
scanf("%d",&a);
h.push(a);//插入
}
else if(a==2)
printf("%d\n",h.top());//堆顶元素
else h.pop();//弹出
}
}
堆的经典例题:
洛谷:P1090 合并果子