面試題41:數據流中的中位數
一、題目描述
如何得到一個數據流中的中位數?如果從數據流中讀出奇數個數值,那麼中位數就是所有數值排序之後位於中間的數值。如果從數據流中讀出偶數個數值,那麼中位數就是所有數值排序之後中間兩個數的平均值。
所謂數據流,就是不會一次性讀入所有數據,只能一個一個讀取,每一步都要求能計算中位數。
二、問題分析
相信上一道題 最小的k個數 給了你容器的啓示。
我們將讀入的數據分爲兩部分,一部分數字小,另一部分大。
小的一部分採用大頂堆存放,大的一部分採用小頂堆存放。當總個數爲偶數時,使兩個堆的數目相同,則中位數=大頂堆的最大數字與小頂堆的最小數字的平均值;而總個數爲奇數時,使小頂堆的個數比大頂堆多一,則中位數=小頂堆的最小數字。
關於插入,我們需要好好思量思量:
- 假如已讀取的個數爲偶數(包括0)時,兩個堆的數目已經相同,一般將新讀取的數插入到小頂堆中,從而實現小頂堆的個數多一。但是,如果新讀取的數字比大頂堆中最大的數字還小,要將新數字插入到大頂堆中,並且將大頂堆中的最大數字插入到小頂堆中,從而實現小頂堆的個數多一。
- 若已讀取的個數爲奇數時,小頂堆的個數多一,一般要將新讀取數字插入到大頂堆中,但是的處理方法與上面類似。
拓展一下:最大最小堆可以用PriorityQueue實現,PriorityQueue默認是一個小頂堆,通過傳入自定義的Comparator函數可以實現大頂堆:
PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(11,new Comparator<Integer>(){ //大頂堆,容量11
@Override
public int compare(Integer i1,Integer i2){
return i2-i1; //降序排列
}
});
PriorityQueue是JDK內置的,想多點了解可以參考【JDK源碼剖析】Queue–隊列 PriorityQueue–優先隊列
三、問題解答
PriorityQueue<Integer> minHeap = new PriorityQueue<>(); //小頂堆,默認容量爲11
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(11,new Comparator<Integer>(){ //大頂堆,容量11
public int compare(Integer i1,Integer i2){
return i2-i1;
}
});
public void Insert(Integer num) {
if(((minHeap.size()+maxHeap.size())&1 )==0){
//偶數時,下個數字加入小頂堆
if(!maxHeap.isEmpty() && maxHeap.peek() > num){
maxHeap.offer(num);
num=maxHeap.poll();
}
minHeap.offer(num);
} else {
//奇數時,下一個數字放入大頂堆
if(!minHeap.isEmpty() && minHeap.peek() < num){
minHeap.offer(num);
num=minHeap.poll();
}
maxHeap.offer(num);
}
}
// 獲取以有的數據的中位數
public Double GetMedian() {
if((minHeap.size()+maxHeap.size())==0) {
throw new RuntimeException();
}
double median;
if((minHeap.size()+maxHeap.size()&1)==0){
median=(maxHeap.peek()+minHeap.peek())/2.0;
}else{
median=minHeap.peek();
}
return median;
}