1. 程式人生 > >資料結構——優先佇列與堆

資料結構——優先佇列與堆

什麼是優先佇列?

普通佇列:先進先出,後進後出 優先佇列:出隊順序和入隊順序無關;和優先順序相關,如醫院中,病重先安排病床

優先佇列的現例項子:

①cpu任務排程,動態選擇優先順序最高的任務執行 ②王者榮耀中,攻擊鍵優先打敵人,無敵人時候優先打最近的小兵

關於優先佇列的實現:

普通的線性結構和堆都可以作為優先佇列的底層實現。

堆實現優先佇列,在最壞的情況下,入隊與出隊的操作,時間複雜都為O(logn)

二叉堆

①堆是一棵被完全二叉樹

②堆中某個節點的值總是不大於其父節點的值,圖示為最大堆

補充:在堆中,所有節點的值都大於他們的孩子節點,那麼層次比較低的節點一定大於層次比較高的節點呢?

不一定,如16、13和19。

可以使用一個數組表示而不需要使用鏈,陣列如圖所示:

父子索引之間的關係
parent(i) = i / 2;
left  child(i) = 2 * i;
right child(i) = 2 * i + 1;

索引也可以從0開始,但公式有所變化

父子索引之間的關係
parent(i) = (i - 1 ) / 2;
left  child(i) = 2 * i + 1;
right child(i) = 2 * i + 2;

大根堆中

向堆中新增一個元素(Sift Up上浮)

先新增,再上浮【滿足父節點一定大於新新增的節點,否則(while)兩者交換位置,並更新索引值】

    /**
     * 向堆中新增元素
     */
    public void add(T t) {
        data.addLast(t);
        siftUp(data.getSize() - 1);
    }
    //上浮
    private void siftUp(int i) {
        while (i > 0 && data.get(i).compareTo(data.get(parent(i))) > 0) {
            //交換位置
            data.swap(i, parent(i));
            //更新i值
            i = parent(i);
        }
    }

堆中移除最大一個元素後(Sift Up下沉)

移除最大元素後,實際上變為了52和30為根節點的子樹,為了簡化操作,將索引值最大的元素size-1複製到索引0的位置,再刪除size-1的元素,這樣總的元素個數就保證了。

 那麼造成一個問題,16元素作為父節點並不大於它的兩個子節點,所以需要有Sift Up下沉的動作,直到滿足父節點元素值大於子節點元素值。

 每次我們需要下沉的元素都和子孩子比較,選擇兩個孩子中最大的元素,如果最大的元素比父節點更大的話,則子節點與父節點交換位置,此時父節點52就一定比左右節點都大了。

此時再將16與他的最大子節點進行比較,如果仍不滿足父節點大於子節點的話,繼續交換位置。

此時再次比較16所處位置的子節點,滿足父節點大於子節點,不需要交換位置,下沉操作結束Sift down。

    /**
     * 檢視堆中最大元素
     */
    public T findMax() {
        if (data.getSize() == 0)
            throw new IllegalArgumentException("堆中沒有元素");
        return data.get(0);
    }

    /**
     * 取出堆中最大元素
     */
    public T extractMax() {
        T t = findMax();
        /**
         *  將堆中最大的元素刪除
         *  1、將最後一個元素與第一個元素交換位置
         *  2、刪除最後一個元素
         *  3、對索引0元素執行siftDown過程
         */
        data.swap(0, data.getSize() - 1);
        data.removeLast();
        siftDown(0);
        return t;
    }

    /**
     * 元素下沉操作(大值上浮,小值下沉)
     * 0、當該節點下是否具有左子節點:
     * 1、如果該節點存在具備右子節點,且右子節點大於左子節點,記錄下右子節點索引
     * 2、如果不存在右子節點或右子節點小於左子節點,記錄下左子節點索引
     * 3、此時記錄下的索引值一定是最大值,與其父節點做比較,如果父節點小於該值,交換值並更新索引index
     */
    private void siftDown(int index) {
        //0
        while (leftChild(index) < data.getSize()) {
            int tag = leftChild(index);
            //1
            if (tag + 1 < data.getSize() && data.get(tag + 1).compareTo(data.get(tag)) > 0) {
                tag ++;
            }
            //2、3 tag
            if(data.get(tag).compareTo(data.get(index)) > 0){
                data.swap(tag,index);
                index = tag;
            }
        }
    }

add和extractMax時間複雜度都是O(logn),同二叉樹的高度。因為堆是一個完全二叉樹,永遠不會退化成一個連結串列。

replace

取出最大的元素後,再放入一個新的元素。

實現思路:①先執行一次extractMax的操作,再執行一次add操作,實際上是兩次O(logn)的操作

                   ②可以將堆頂的元素替換為新增元素,再呼叫一次siftDown,實際上是一次O(logn)操作

    /**
     * 先取出堆中最大元素,再新增新元素
     * 將堆頂的元素替換為新增元素,再呼叫一次siftDown()
     */
    public T replace(T t){
        T t1 = findMax();
        data.add(0,t);
        siftDown(0);
        return t1;
    }

heapify

將任意陣列整理成堆的形狀

①將給定的陣列看成一個完全二叉樹

②從最後一個非葉子節點開始下沉siftDown(),即從索引4位置不斷向前執行siftDown()操作

plus:最後一個非葉子節點的索引為:獲得最後一個索引,計算出其父節點索引。

對於索引從0開始的完全二叉樹,其父節點索引為(data.getSize() - 1 ) / 2,從1開始為data.getSize() / 2

③索引4上的22小於其子節點(索引為9的62),交換位置,下沉操作結束。再看索引為3所在節點,其左右節點最大值為41大於13,則41與13交換位置,下沉操作結束。再看索引為2所在節點,其左右節點最大值為28大於19,則28與19交換位置,下沉結束

④對於索引為1的節點17,它的最大子節點為62大於17,則62與17交換位置,此時17來到了索引為4的位置,由於17下還有子節點22且大於17,17與22交換位置,此時17來到了索引為9的位置。

⑤索引為0的節點15,其最大子節點為62大於15,則15與62交換位置,此時15來到了索引為1的位置,由於其下面還有子節點,索引為1下的子節點最大值為41大於15,則41與15交換位置,此時15來到了索引為3的位置,由於其下面還有子節點,接著下沉索引為3下的子節點最大值為30大於15,30與15交換位置,此時15來到了索引為7的位置,由於索引為7不存在子節點,下沉結束。

自此位置,heapify過程完成了,陣列變成了一個最大堆。我們在heapfiy之前就排除了所有葉子節點,相當於一次拋棄了一半的節點,再進行siftDown操作,相比從一個空堆開始一個個新增元素要快很多(因為對每一個元素都要執行O(logn)的操作,相當於O(nlogn)級別的操作,而heapify的過程,時間複雜度為0(n)級別)

    //Array類中
    public Array(E[] arr){
        data = (E[]) new Object[arr.length];
        for(int i = 0 ; i < arr.length ; i ++)
            data[i] = arr[i];
        size = arr.length;
    }
    public MaxHeap(T[] arr){
        //構建一個完全二叉樹
        data = new Array<>(arr);
        //從最後一個非葉子節點開始向前siftDown
        for(int i = parent(arr.length - 1) ; i >= 0 ; i--)
            siftDown(i);
    }