大小頂錐動態求解中位數以及如何動態求解TopK

問題一:一個有序數組(從大到小) 長度爲13  中位數爲 18 

int arr[] = new int[]{1, 3, 5, 6, 11, 14, 18, 21, 27, 29, 31, 56, 59}

但是我們這個數組是動態的,每次都插入一個數據,這樣沒插入依次中位數就要破環,而且還要每次排序,這樣會很慢。

我們思路是這樣維護一個大小,頂堆。 大頂堆存放0-5 元素,小頂堆 存放 6到12 ,這樣小頂堆的堆頂就是中位數。

 

爲題二:如何求解一個動態數組中,取出排名前k的數據;舉一個現實問題,百度文章搜索量如何求動態求排名前10位的文章(文中的搜索量是不斷變化的,你買次都遍歷求topK是非常耗時間的)

大頂堆:堆頂元素是整個集合中最大的元素,同理 小頂堆是整個集合最小的元素。

這裏 我們採用數組形式實現頂錐(另一種實現方式是鏈表形式)

大頂堆代碼:

package com.jxd.test;

public class BigHeap {

    private int heapCont[];

    /**
     * 堆中能存儲的最大數據個數
     */
    private int tab;

    /**
     * 堆中已經存在數據的個數
     */
    private int count;

    public BigHeap(int capacity) {
        heapCont = new int[capacity + 1];//因爲0號位沒法使用
        tab = capacity;
        count = 0;
    }

    /**
     * n表示原數組中有數據個數
     *
     * @param heapCont
     * @param n
     */
    public BigHeap(int[] heapCont, int n) {
        this.heapCont = heapCont;
        tab = heapCont.length;
        count = n;
        blindHeap(count);
    }

    /**
     * 向堆中插入數據
     * 從下往上法
     */
    public boolean insert(int data) {

        if (count >= tab) return false;//堆中數據滿了

        heapCont[++count] = data;//++count 這裏注意下,實際是先自增。
        int i = count;
        int index = 0;
        while ((index = i / 2) > 0 && heapCont[i] >= heapCont[index]) {
            swap(heapCont, i, index);
            i /= 2;
        }
        return true;
    }


    /**
     * 建堆的過程,把一個原數組建成堆
     * n表示數據的個數
     * @param n
     */
    private void blindHeap(int n) {
        for (int i = n / 2; i >= 1; i--) {
            heapify(i, n);
        }
    }

    /**
     * 獲取堆頂元素
     *
     */
     int getTop() {
        if(count==0)return -1;
        return heapCont[1];
    }



    /**
     * 刪除堆頂
     * 從上往下法
     */

    public int removeTop() {
        if (count == 0) return -1;
        int i = 1;
        int top=heapCont[i];
        heapCont[i] = heapCont[count--];//最後一個元素放到堆頂,然後count-- 相當於刪除一個數據
        heapify(i, count);
        return top;
    }

    /**
     * 堆化
     *
     * @param i
     * @param n
     */
    private void heapify(int i, int n) {
        int index;
        while (true) {
            int max = i;
            /**
             * 實際就是判斷以下左子樹和右子樹誰大。從而決定走哪一個
             */
            if ((index = i << 1) <= n && (heapCont[i] < heapCont[index])) //比較左子樹
                max = index;  //i << 1 等於i乘以2

            if ((index = (i << 1) + 1) <= n && (heapCont[max] < heapCont[index])) //左子樹和右子樹比較
                max = index;

            if (max == i) break;//滿足堆的數據結構,父節點大於等於子節點
            swap(heapCont, i, max);
            i = max;//繼續下一次循環
        }

    }

    /**
     * 堆的排序 從小到大。
     * count表示元素的個數
     */

    public void sort() {

        int k = count;
        while (k > 1) {
            swap(heapCont, 1, k);
            k--;
            heapify(1, k);
        }
    }


    /**
     * 數組的互換方法
     * 下標source 和target互換
     *
     * @param a
     * @param source
     * @param target
     */
    private void swap(int a[], int source, int target) {
        int sindex = a[source];
        int tindex = a[target];
        a[source] = tindex;
        a[target] = sindex;
    }

    public static void main(String[] args) {
        BigHeap heap = new BigHeap(10);
        heap.insert(10);
        heap.insert(20);
        heap.insert(18);
        heap.insert(30);
        heap.insert(39);
        heap.insert(22);
        heap.insert(6);
        heap.insert(4);
        heap.insert(51);
        heap.insert(33);
        heap.removeTop();//刪除頂
        heap.sort();
        heap.insert(53);
        System.out.println("");
    }
}

小頂堆代碼: 和大頂堆代碼一樣,就把代碼中的> 改成 <就可以

 

測試 代碼:

 

package com.jxd.test;

public class testHeap {
    public static void main(String[] args) {

        int arr[] = new int[]{1, 3, 5, 6, 11, 14, 18, 21, 27, 29, 31, 56, 59};//13

        BigHeap bigHeap = new BigHeap(10);
        for (int i = 0; i < 6; i++) {
            bigHeap.insert(arr[i]);
        }

        SmallHeap smallHeap = new SmallHeap(10);
        for (int i = 6; i < arr.length - 1; i++) {
            smallHeap.insert(arr[i]);
        }
        System.out.println("中位數爲" + smallHeap.getTop());

        //動態維護數組中的中位數。 我們新添加一個元素 61 {1,3,5,7,6,11,14,18,21,27,29,31,56,59} 14
        int node = 7;

        if (bigHeap.getTop() >= node) {//進入大頂堆
            int top =bigHeap.removeTop();//把小大頂堆堆頂放到小頂堆中
            bigHeap.insert(node);//插入新數據
            smallHeap.insert(top);
        }
        else if (smallHeap.getTop() < node) {//進入小頂堆
            int top =smallHeap.removeTop();
            smallHeap.insert(node);
            bigHeap.insert(top);
        }

        System.out.println("添加元素"+node +"之後的中位數爲" + smallHeap.getTop());
    }
}

測試結果:

中位數爲18
添加元素7之後的中位數爲 14

問題二:

 

 

package com.jxd.test;

public class testHeap {
    public static void main(String[] args) {

        int arr[] = new int[]{18, 3, 9, 31, 22, 16, 15, 19, 51, 88, 14, 33, 38};//無序數組
        int k = 3;
        //比如我們排名k的數據
        SmallHeap heap = new SmallHeap(k);
        heap.insert(arr[0]);
        heap.insert(arr[1]);
        heap.insert(arr[2]);//先插入前邊三個數據
        for (int i = 3; i < arr.length; i++) {
            if(heap.getTop()<arr[i]){//如果數據大於堆頂
                heap.removeTop();
                heap.insert(arr[i]);
            }
        }
        System.out.println("排名前"+k+"數據" +bigHeap.getTop());//這裏你斷點調試下 都在SmallHeap 數組中
        
        //動態數據 比如我們添加一個數據 位99 我們先不着急添加到數組中,先在小頂堆中看看是否大於小頂堆的 堆頂元素
        int  node =99;
        if(heap.getTop()<node){
            heap.removeTop();//刪除堆頂 然後添加這個新元素
            heap.insert(node);
        }
        int arrNew[] = new int[]{18, 3, 9, 31, 22, 16, 15, 19, 51, 88, 14, 33, 38,99};//無序數組
        System.out.println("動態排名前"+k+"數據"+heap.getTop() );//這裏你斷點調試下 都在SmallHeap 數組中
    }
}

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章