提出这样一个问题:
输入一个序列a[1..n]进行m次操作
·操作1:将x插入到数列中
·操作2:输出最小值
·操作3:将最小值删除
(n,m<=100000)
先尝试按照普及组的思路去做
方法1:无序表维护
·插入O(1)
·删除O(1)
·查询O(n)
方法2:有序表维护
·插入O(n)
·删除O(n)
·查询O(1)
很显然线性结构已经不满足这道题目的要求,然而我们可以用堆(heap)来解决这个问题
堆是一棵完全二叉树,完全二叉树可以用便捷的方式表示父子关系,即u的儿子是u*2和u*2+1,且树的高度是O(logn)的
规定一个点的值必须大于或小于他的儿子节点的值(即小根堆/大根堆)
很显然,此时极值是堆的根节点1,对于其他节点,我们只知道父亲和儿子之间的大小关系
之前说过树的高度是O(logn)的,我们可以利用这一点把总复杂度降到O(mlogn)
对于插入一个数,我们可以把这个数直接放在堆的叶节点上,然后通过上调操作最多O(logn)时间内调整到符合堆性质的位置上
以小根堆为例
·如果这个节点的值大于它父亲节点的值则说明不需要再上调了,调整结束
·如果这个节点的值小于它父亲节点的值就将它和它父亲节点的值交换
上调示意图:
对于删除一个数,我们可以把他和堆中最后一个节点交换,然后直接删去节点,并将交换上来的节点调整到应该要的位置
此时除了上调操作还需要下调操作(如果是删除根结点的话无需上调)
还是用小根堆为例
·如果一个节点小于他们两个儿子之间最小值则说明不需要下调了,调整结束
·否则就将该节点和两个儿子之间的最小值交换
删除最小值示意图:
这样就解决了刚才的题目,至于具体实现详见代码
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
const int maxn=1000000;
const int inf=2147364748;
int i,j,n,size,p[maxn],heap[4*maxn],g,x;
bool cmp(int x,int y){return x<y;}
void up(int x)
{
while(heap[x]<heap[x/2])
{
swap(heap[x],heap[x/2]);
x=x/2;
}
}
void down()
{
int x=1;
while(x*2<=size)
{
x=x*2;
if(heap[x+1]<heap[x]&&x+1<=size) x++;
if(heap[x]<heap[x/2]){swap(heap[x],heap[x/2]);}else return;
}
}
int main()
{
scanf("%d",&n);
heap[0]=-inf;
for(i=1;i<=n;i++)
{
scanf("%d",&g);
if(g==1)
{
scanf("%d",&x);
heap[++size]=x;
up(size);
}
if(g==2)printf("%d\n",heap[1]);
if(g==3)
{
heap[1]=heap[size];
size--;
down();
}
}
return 0;
}