#include <iostream>
#include <stdio.h>
#include <set>
#include <vector>
#include <algorithm>
using namespace std;
/*
问题:随机生成一些数字并传入某个方法。编写一个程序,每当收到新数字时,找出并记录中位数。
分析:题目的意思应该是:根据随机生成了k个数字之后,接下来每次再收到新数字时,找到中位数。
简化问题:寻找中位数,什么是中位数?就是数组A排序之后,假设共有n个数字,
如果n为奇数,那么A[n/2]即为中位数
如果n为偶数,那么A[n/2 - 1] , A[n/2 - 1]累加之后除以2。假设我事先对之前的随机生成的元素组成的数组A排序,
不妨假设不管n为奇数或者偶数,寻找的中位数都是A[n/2],那么我可以事先取出中间3个数A[n/2 - 1],A[n/2],A[n/2+1]
然后用新生成的数字和A[n/2]比较,
如果A[n/2] > 生成的数字,那么新的中位数直接就是A[n/2 - 1]
如果A[n/2] < 生成的数字, A[n/2 + 1]
= A[n/2]
书上解法:
用两个优先级堆,大顶堆:存放小于中位数的值,小顶堆:存放大于中位数的值。则大顶堆的对顶是最大值,小顶堆的对顶是最小值。
如果:大顶堆的大小 > 小顶堆的元素大小,则大顶堆的堆顶为中位数
= ,则大小顶堆的平均值为中位数。
只需要使得大顶堆的大小>=小顶堆的大小。
有新元素生成,如果 新元素 <= 中位数,放入大顶堆(即包含较小元素的那一堆);
> 小
如果插入新元素后,大顶堆元素个数 >= 小顶堆元素个数+2,说明较小的那一堆元素个数多了,将大顶堆的对顶移动到小顶堆
大顶堆元素个数 < 小顶堆元素个数,将小顶堆的对顶移动到大顶堆
输入:
4(初始元素个数)
1 4 7 10
3(后续生成的新元素个数)
3 2 8
输出:
4.00(后续第一个新元素加入后的中位数,保留两位小数)
3.50
4.00
关键:
1
用两个优先级堆,大顶堆:存放小于中位数的值,小顶堆:存放大于中位数的值。则大顶堆的对顶是最大值,小顶堆的对顶是最小值。
如果:大顶堆的大小 > 小顶堆的元素大小,则大顶堆的堆顶为中位数
= ,则大小顶堆的平均值为中位数。
只需要使得大顶堆的大小>=小顶堆的大小。
有新元素生成,如果 新元素 <= 中位数,放入大顶堆(即包含较小元素的那一堆);
> 小
如果插入新元素后,大顶堆元素个数 >= 小顶堆元素个数+2,说明较小的那一堆元素个数多了,将大顶堆的对顶移动到小顶堆
大顶堆元素个数 < 小顶堆元素个数,将小顶堆的对顶移动到大顶堆
*/
class CompareMax
{
public:
bool operator()(const int a ,const int b)const
{
return a > b ? true : false;
}
};
class CompareMin
{
public:
bool operator()(const int a ,const int b)const
{
return a < b ? true : false;
}
};
//利用初始的数组产生两个堆,大顶堆存放较小部分元素,小顶堆存放较大部分元素,且满足: 小顶堆元素个数<= 大顶堆元素个数 <= 小顶堆元素个数+1
void generateSet(vector<int>& datas , multiset<int , CompareMax >& maxHeap , multiset<int , CompareMin >& minHeap )
{
if(datas.empty())
{
return;
}
//初始先排序
sort(datas.begin() , datas.end());
int size = datas.size();
int middleNum = (size + 1) / 2;
//将前半段小的放入到大顶堆中
for(int i = 0 ; i < size ; i++)
{
int value = datas.at(i);
if(i < middleNum)
{
maxHeap.insert(value);
}
else
{
minHeap.insert(value);
}
}
}
double getMiddleNum(multiset<int , CompareMax >& maxHeap , multiset<int , CompareMin >& minHeap )
{
int minSize = maxHeap.size();
int maxSize = minHeap.size();
double originalMiddleNum = 0;
//计算原始中位数,如果总元素个数是奇数个,那么原始中位数等于大顶堆的堆顶
if( ( (minSize + maxSize) & 1 ) == 1)
{
originalMiddleNum = *(maxHeap.begin());
}
else
{
originalMiddleNum = ( *maxHeap.begin() + *minHeap.begin() ) / 2.0;
}
return originalMiddleNum;
}
//根据输入的新元素,大顶堆和小顶堆,生成中位数
double generateMiddleNum(int value, multiset<int , CompareMax >& maxHeap , multiset<int , CompareMin >& minHeap )
{
double originalMiddleNum = getMiddleNum(maxHeap , minHeap);
//比较如果:新元素 <= 原始中位数,则插入到大顶堆(即较小部分的那一堆元素中)
if( 1.0*value <= originalMiddleNum )
{
maxHeap.insert(value);
}
else
{
minHeap.insert(value);
}
//如果大顶堆元素个数 >= 小顶堆元素个数 + 2 ,就把大顶堆的堆顶移除,并插入到小顶堆中
int minSize = maxHeap.size();
int maxSize = minHeap.size();
if( minSize >= maxSize + 2)
{
int value = *maxHeap.begin();
maxHeap.erase(maxHeap.begin());
minHeap.insert(value);
}
//如果大顶堆元素个数 < 小顶堆元素个数,就把小顶堆的堆顶移除,并插入到大顶堆中
else if(minSize < maxSize)
{
int value = *minHeap.begin();
minHeap.erase(minHeap.begin());
maxHeap.insert(value);
}
originalMiddleNum = getMiddleNum(maxHeap , minHeap);
return originalMiddleNum;
}
void process()
{
int originalNum , addNum ;
vector<int> datas;
vector<int> newDatas;
int value;
multiset<int , CompareMax > maxHeap;
multiset<int , CompareMin > minHeap;
double middleNum;
cout.setf(ios::fixed);
cout.precision(2);
while(cin >> originalNum )
{
datas.clear();
newDatas.clear();
maxHeap.clear();
minHeap.clear();
for(int i = 0 ; i < originalNum ; i++)
{
cin >> value;
datas.push_back(value);
}
//生成初始的大顶堆和小顶堆
generateSet(datas , maxHeap , minHeap);
cin >> addNum;
for(int i = 0 ; i < addNum ; i++)
{
cin >> value;
//接下来对每次新输入的元素,生成中位数
middleNum = generateMiddleNum(value , maxHeap , minHeap);
cout << middleNum << endl;
}
}
}
int main(int argc , char* argv[])
{
process();
getchar();
return 0;
}