题目
随时找到数据流中的中位数:有一个源源不断地吐出整数的数据流,假设你有足够的空间来保存吐出的数。请设计一个名叫MedianHolder的结构,MedianHolder可以随时取得之前吐出所有数的中位数。
要求:
1)如果MedianHolder已经保存了吐出的N个数,那么任意时刻将一个新数加入到MedianHolder的过程,其时间复杂度是O(logN)。
2)取得已经吐出的N个数整体的中位数的过程,时间复杂度为O(1)。
题意:
比如当前数据流式5 3 6 0 7 ,那么中位数字就是排序后的
0 3 5 6 7 的中位数5.
不能每次插入一个数据就排序,然后再取中位数字,因为这样是n*nlogn 的复杂度。nlogn是排序的最低的复杂度了。
思路:建立一个大根堆,一个小根堆, 定义一个数据插入规则, 比如当前数据个数是偶数的时候就把新数据插入到小根堆,当前数据个数是奇数的时候把数据插入到大根堆。
更重要的是大根堆的堆顶比小根堆的堆顶的数字要小
这样当数据的总数是奇数的时候,中位数字直接就是小根堆的“**堆顶”**而当数据的总数是偶数的时候中位数字是(大根堆堆顶 + 小根堆堆顶/2
前面说了当数据个数是偶数个的时候要把新数据插入到小根堆,如果
新数据比大根堆的堆顶还要小的话,那么就先把这个新的数据插入到大根堆,从大根堆的堆顶弹出一个数字,再把这个数字插入到小根堆,这样就保证了小根堆的数字都是大于等于大根堆的数字的!
同理当数据总数是奇数的时候要把新数据插入到大根堆,如果这个新的数据比小根堆的堆顶还要大的话,得把这个新数据插入到小根堆,然后调节小根堆,弹出小根堆的堆顶这个数字,再把这个数字压入到大根堆。这样做还是为了保证大根堆的数字是小于等于小根堆的数字的!
#include<iostream>
#include<climits>
#include<vector>
#include<map>
#include<cstdio>
#include<queue>
#include<string>
#include<cstring>
#include<stack>
#include<queue>
#include<cmath>
#include<set>
#include<algorithm>
using namespace std;
class MedianHolder{
public :
vector<int>smallHeap;//最小堆
vector<int>bigHeap;//最大堆
int lenOfsmallHeap;//最小堆数据个数
int lenOfbigHeap;//最大堆数据个数
MedianHolder(){
lenOfsmallHeap = 0;
lenOfbigHeap = 0;
}
void solve(int num){//插入数据
int length = lenOfsmallHeap+ lenOfbigHeap;
if(length & 1){//奇数个数的话插入最大堆
if(lenOfsmallHeap > 0 && num > smallHeap[0]){
insertsmallHeap(num);//插入最小堆并调整最小堆
int tmp = smallHeap[0];//获取最小堆的堆顶
movesmallVector();//弹出最小堆堆顶
//弹出最小堆的堆顶后重新调整最小堆
for(int i = lenOfsmallHeap / 2 - 1; i >= 0; i--)
adjustsmallHeap(i);
insertbigHeap(tmp);//插入最大堆并调整最大堆
}
else
insertbigHeap(num);
}
else{//偶数个数的话插入最小堆, 同上的做法
if(lenOfbigHeap > 0 && num < bigHeap[0] ){
insertbigHeap(num);
int tmp = bigHeap[0];
movebigVector();
for(int i = lenOfbigHeap / 2 - 1; i >= 0; i--)
adjustbigHeap(i);
insertsmallHeap(tmp);
}
else{
insertsmallHeap(num);
}
}
}
double getMedian(){//获取挡圈数据流的中位数字
int length = lenOfsmallHeap + lenOfbigHeap;
if(length == 0)
return 0;
if(length & 1)//数据流的数据个数是奇数个
return smallHeap[0];
else{//偶数个
return (smallHeap[0] + bigHeap[0]) * 1.0 / 2.0;
}
}
//调整最小堆
void adjustsmallHeap(int index){
int left = index * 2 + 1;
int right = index * 2 + 2;
int minIndex = index;
if(left < lenOfsmallHeap && smallHeap[minIndex] > smallHeap[left])
minIndex = left;
if(right < lenOfsmallHeap && smallHeap[minIndex] > smallHeap[right])
minIndex = right;
if(minIndex != index){
swap(smallHeap[index], smallHeap[minIndex]);//交换,把小的数字放上面
adjustsmallHeap(minIndex);//递归调整
}
return ;
}
void adjustbigHeap(int index){//调整最大堆,同上最小堆
int left = index * 2 + 1;
int right = index * 2 + 2;
int maxIndex = index;
int lenOfbigHeap = bigHeap.size();
if(left < lenOfbigHeap && bigHeap[maxIndex] < bigHeap[left])
maxIndex = left;
if(right < lenOfbigHeap && bigHeap[maxIndex] < bigHeap[right])
maxIndex = right;
if(maxIndex != index){
swap(bigHeap[index], bigHeap[maxIndex]);
adjustbigHeap(maxIndex);
}
return ;
}
void insertsmallHeap(int num){//插入数据到最小堆并调节最小堆
smallHeap.push_back(num);
lenOfsmallHeap++;//堆长度增加
for(int i = lenOfsmallHeap / 2 - 1; i >= 0; i--)
adjustsmallHeap(i);//从最后一个非叶子节点开始调节最小堆
}
void insertbigHeap(int num){//插入数据到最大堆并调节最大堆
//同上最小堆
bigHeap.push_back(num);
lenOfbigHeap++;
for(int i = lenOfbigHeap / 2 - 1; i >= 0; i--)
adjustbigHeap(i);
}
//弹出最小堆的堆顶
void movesmallVector(){
//把1号位置下方的元素向前移动一位,相当于弹出堆顶。
for(int i = 1; i < lenOfsmallHeap; i++)
smallHeap[i - 1] = smallHeap[i];
smallHeap.pop_back();//弹出了堆顶,前面元素整体左移了一位, 少一个元素,尾部不要了
lenOfsmallHeap--;//堆长度减少
}
//弹出最大堆的堆顶,同上最小堆
void movebigVector(){
for(int i = 1; i < lenOfbigHeap; i++)
bigHeap[i - 1] = bigHeap[i];
bigHeap.pop_back();
lenOfbigHeap--;
}
};
int main()
{
MedianHolder Mobj;
vector<int>test;
int x;
while(true){
cout<<"输入加入的数据: ";
cin>>x;
test.push_back(x);
Mobj.solve(x);
cout<<"当前数据序列是 ";
for(int i = 0; i < test.size(); i++)
cout<<test[i]<<" ";
cout<<" 当前序列的中位数是: "<<Mobj.getMedian()<<endl;
}
system("pause");
}
测试一:
测试二:无序的数据: