题目描述:如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
分析:数据是从数据流中读出来的,它的数目随着时间的变化而增加。如果用一个容器来保存从数据流中读出来的数据,则当有新的数据从流中读出来时,这些数据就插入数据容器。可以考虑用数组来作为保存数据的容器,在已经排序好的数组中找出中位数的操作需要O(1)的时间复杂度,而向数组中来插入一个数据的时间复杂度为O(n)。
整个容器可以被分为两个部分,位于容器左边的部分的数据比右边的数据小。另外P1指向左边部分最大的数,P2指向右边数据最小的数。而且左右两边容器的个数相差最大为1。这样我们可以维护一个最大堆实现左边的数据容器,用一个最小堆实现右边的容器。向堆中插入一个数据的时间复杂度为O(logn),而取堆顶数据的时间复杂度为O(1)。可以较好的解决这个题目。
代码:
#include <vector>
#include <algorithm>
using namespace std;
template<typename T>
class DynamicArray{
private:
vector<T> min; //最小堆 一开始为空 满足成堆的要求
vector<T> max; //最大堆
public:
void Insert(T num){
//大根堆与小根堆的元素个数相差不超过1。我们规定:当此时元素的总个数为偶数时,新元素插入小根堆;总个数为奇数时,插入大根堆
if(((min.size() + max.size()) % 2) == 0){ //如果元素的总个数为偶数 新元素默认放入小根堆中
if(max.size() > 0 && num < max[0]){ //如果新元素小于大根堆最大值,那么先将元素插入大根堆,然后取出大根堆的最大值插入小根堆中
max.push_back(num);
push_heap(max.begin(),max.end(),less<T>());
num = max[0]; //取出大根堆中的最大值
pop_heap(max.begin(),max.end(),less<T>());
max.pop_back();
}
min.push_back(num); //将最大值插入小根堆中,保证大根堆中的值都要小于小根堆中的最小值
push_heap(min.begin(),min.end(),greater<T>());
}
else{ //如果元素总个数为奇数,新元素放入大根堆中
if(min.size() > 0 && min[0] < num){ //如果新元素的值比小根堆的最小值大,那么先将元素插入小根堆中,然后取出小根堆的最小值插入大根堆中
min.push_back(num);
push_heap(min.begin(),min.end(),greater<T>());
num = min[0]; //取出小根堆中的最小值
pop_heap(min.begin(),min.end(),greater<T>());
min.pop_back();
}
max.push_back(num); //插入大根堆中,保证大根堆中的值都要小于小根中的最小值
push_heap(max.begin(),max.end(),less<T>());
}
}
T GetMedian(){
int size = min.size() + max.size();
if(size == 0)throw exception();
T median = 0;
if((size & 1) == 1){
median = min[0]; //如果元素的总个数为奇数,那么中位数在最小堆中,为min[0]
}
else {
median = (min[0] + max[0]) / 2; //如果元素的总个数为偶数,那么中位数为(min[0] + max[0]) / 2
}
return median;
}
};
简单的main函数调用:
#include <iostream>
int main(){
DynamicArray<int> arr;
for(int i = 0;i < 100;i++){
arr.Insert(i);
int meidan = arr.GetMedian();
std::cout<<"中位数是:"<<meidan<<std::endl;
}
return 0;
}