题目描述:
设计一个数据结构,初始为空,支持以下操作:
(1)增加一个元素,要求在log(n)时间内完成,其中n是该数据结构中当前元素的个数。注意:数据结构中允许有重复的元素。
(2)返回当前元素集合的中位数,要求在常数时间内完成。如果当前元素的个数为偶数,那么返回下中位数(即两个中位数中较小的一个)。
(3)删除中位数,要求在log(n)时间内完成。
输入
输入的第一行是一个自然数T,代表测试数据的组数((1 ≤ T ≤ 600))。每组测试数据的第一行是个自然数N,代表操作的次数,1<=N<=10000。后面的N行中的每行代表一个操作,每次操作首先输入一个单字符代表操作的类型:
I表示插入,后面跟着输入一个正整数(这是唯一带有输入数值的操作)。
Q表示查询,输出当前的中位数(这是唯一产生输出的操作)。
D表示删除当前的中位数。
输入保证是正确的:查询时集合保证不为空(即中位数是存在的),删除时保证集合中有足够可供删除的元素。
输出
每次查询操作Q时输出的中位数,每次输出单独占一行。
样例输入
1
8
I 4
I 3
I 5
Q
D
I 10
I 2
Q
样例输出
4
3
解题思路
用一个大顶堆一个小顶堆即可解决上述问题。如下图所示:
大顶堆可以用STL中的优先队列priority_queue实现,模板声明需要带有三个参数:
priority_queue<Type,Container,Functional>
Type为数据类型,Container为保存数据的容器,Functional为元素的比较方式。其中Container必须为用数组实现的容器,如vector,但不能用list,STL里默认用vector,比较方式默认用 operator<,所以如果把后面两个参数缺省的话,优先队列是大顶堆,队首元素最大;STL里面定义了一个仿函数,greater<>,对于基本类型可以用这个仿函数声明小顶堆:
priority_queue<int, vector<int>, greater<int> > q
vector与greater所在的头文件为
#include<vector>
#include<functional>
对于输入的数据,一半较小的元素保存在大顶堆中,另一半较大的元素保存在小顶堆中,大顶堆的堆顶元素小于等于小顶堆的堆顶元素,两个堆元素的数目相差不超过1,这样对于中位数的获取或者删除操作能够从堆顶元素获得。
算法具体实现:
对于插入操作,判断两个堆是否有一个为空,若为空则需要压入大顶堆,即左边的堆中,如果不为空,则需要把输入值与两个堆顶元素进行比较,如果比小顶堆的元素要小,则压入大顶堆中,如果比小顶堆的元素要大,则压入小顶堆中:
if (maxheap.empty() || minheap.empty())
{
maxheap.push(value);
}
else
{
if (value < minheap.top())
maxheap.push(value);
if (value>=minheap.top())
minheap.push(value);
}
这样操作完成之后,则左边的大顶堆比右边的小顶堆元素数目多1或者右边的元素数目比左边多1,通过以下操作是的大顶堆元素比小顶堆元素多1或者相等:
//进行排列,使得左边大顶堆始终比右边小顶堆最多多一个元素
if (maxheap.size()> (minheap.size()+ 1))
{
temp_value = maxheap.top();
maxheap.pop();
minheap.push(temp_value);
}
if (minheap.size() > maxheap.size())
{
temp_value = minheap.top();
minheap.pop();
maxheap.push(temp_value);
}
按照这样的方式排序之后,中位数即为大顶堆的堆顶元素(如果为元素为偶数的情况下,返回较小的元素,此时仍符合):
printf("%d\n", maxheap.top());
对于删除中位数的操作,在弹出大顶堆的堆顶元素之后,此时大顶堆的元素数目可能比小顶堆的元素数目少1,所以需要重新进行判断,平衡两个堆的元素:
maxheap.pop();
if (minheap.size() > maxheap.size())
{
temp_value = minheap.top();
minheap.pop();
maxheap.push(temp_value);
}
全部代码:
#include<iostream>
#include<string>
#include<string.h>
#include<queue>
#include<functional>
#include<vector>
using namespace std;
int main()
{
int T;
int N;
int temp_value;
char operation;
priority_queue<int>maxheap;//大顶堆
priority_queue<int, vector<int>, greater<int>>minheap;//小顶堆
scanf("%d", &T);
while (T--)
{
//队列元素清空
while (!maxheap.empty())
maxheap.pop();
while (!minheap.empty())
minheap.pop();
scanf("%d", &N);
while (N--)
{
scanf(" %c", &operation);
//scanf("%d",&operation);//bug,scanf输入字符时care
int value;
switch (operation)
{
case 'I'://插入操作
{
scanf("%d", &value);
if (maxheap.empty() || minheap.empty())
{
maxheap.push(value);
}
else
{
if (value < minheap.top())
maxheap.push(value);
if (value>=minheap.top())
minheap.push(value);
}
//进行排列,使得左边大顶堆始终比右边小顶堆最多多一个元素
if (maxheap.size()> (minheap.size()+ 1))
{
temp_value = maxheap.top();
maxheap.pop();
minheap.push(temp_value);
}
if (minheap.size() > maxheap.size())
{
temp_value = minheap.top();
minheap.pop();
maxheap.push(temp_value);
}
break;
}
//左边大顶堆始终比右边小顶堆多一个元素,直接返回大顶堆元素即可
case 'Q'://查询操作
{
printf("%d\n", maxheap.top());
break;
}
case 'D'://删除操作
{
maxheap.pop();
if (minheap.size() > maxheap.size())
{
temp_value = minheap.top();
minheap.pop();
maxheap.push(temp_value);
}
break;
}
}
}
}
return 0;
}
care
编写程序的过程中有几点需要注意的地方:
1、在进行删除中位数操作之后仍然需要重新对堆进行平衡操作;
2、由于输入元素较多,所以scanf会比cin快很多,在对字符串进行scanf时,需要避免回车输入,若为
scanf("%c", &operation);
则能够把空格作为字符输入,只需要在c%前面加入一个空格即可避免输入回车符号
scanf(" %c", &operation);
嗯,两天A的两道题,超时的原因都是因为用的是cin而不是scanf,scanf对于输入字符的操作有很多需要注意的地方,先挖一个坑。