數據流中的中位數

 

吐出的較小的N/2 個,都在大根堆裏,較大的 N/2 個,都在小根堆裏。java

此時 五、4,都在大根堆,小根堆沒有數。api

此時應該從大根堆的堆頂彈出來,扔到小根堆裏。數組

好比:先把 5 拿出來,再把堆最後一個元素 4 ,放在 5 的位置上。數組長度是 1000,可是如今的 heapsize 是 0~1 。code

因此大根堆裏只有 4 ,小根堆裏是 5blog

把堆頂的數字給彈出來。隊列

先讓 6 與 1 交換位置,而後把 0~ 4 改爲 0~3 ,it

堆頂變小,而後經歷一次向下沉的過程。減堆操做。io

1. 堆頂與最後一個元素交換class

2. 標記越界的 heapsize 減一im

3. 而後從 0 位置開始經歷一個 heapify

又恢復成大根堆了。

 

爲啥是最後一個數和他換,由於原本交換以後 6 是要失效的,heapsize 要減一,因此最好跟最後一個數交換。

兩個堆之間差值 > 1 ,就從較大的一個堆裏,彈出一個數到,另外一個堆裏。

大根堆的堆頂和小根堆的堆頂,必定能搞出中位數來。

大根堆中收集的是較小的 N/2 個的最大值。

小根堆中收集的是較大的 N/2 個的最小值。

來了一個6,比大根堆的 4 大,到小根堆裏去。

當前數不是 <= 大根堆的數,就扔到小根堆裏面去。

當前數 <= 大根堆的數,就扔到大根堆裏面去。

此時要把小根堆中的堆頂扔到 大根堆中去。

策略是固定的,當前數 <= 大根堆堆頂最大值,就扔到大根堆裏去。不平衡了就從大根堆裏把堆頂拿到小根堆裏去。若是小根堆更大,差值 > 2 ,就把小根堆的堆頂,扔到大根堆裏去。

時刻保持着較大的 N/2 在小根堆裏。

時刻保持着較小的 N/2 在大根堆裏。

這樣的話,就能夠隨時拿到中位數了

優先級隊列,就是堆。

由於每一個數它調整的代價只跟層數有關,因此每次增長仍是彈出來,都是O(logN)的。

大根堆增長或者減小一個數,只須要承擔一個 log(N)的代價。

logN 你想象一下,40 億與 32 。

只須要調整高度有關的數就好了,跟其餘數沒有關係的。

 

 

/* 大頂堆,存儲左半邊元素 */
private PriorityQueue<Integer> left = new PriorityQueue<>((o1, o2) -> o2 - o1);
/* 小頂堆,存儲右半邊元素,而且右半邊元素都大於左半邊 */
private PriorityQueue<Integer> right = new PriorityQueue<>();
/* 當前數據流讀入的元素個數 */
private int N = 0;

public void Insert(Integer val) {
    /* 插入要保證兩個堆存於平衡狀態 */
    if (N % 2 == 0) {
        /* N 爲偶數的狀況下插入到右半邊。
         * 由於右半邊元素都要大於左半邊,可是新插入的元素不必定比左半邊元素來的大,
         * 所以須要先將元素插入左半邊,而後利用左半邊爲大頂堆的特色,取出堆頂元素即爲最大元素,此時插入右半邊 */
        left.add(val);
        right.add(left.poll());
    } else {
        right.add(val);
        left.add(right.poll());
    }
    N++;
}

public Double GetMedian() {
    if (N % 2 == 0)
        return (left.peek() + right.peek()) / 2.0;
    else
        return (double) right.peek();
}
相關文章
相關標籤/搜索