優先隊列

一、概念

       優先隊列是用來作什麼事情的?應用程序須要處理優先級的事件,爲每一個事件分配一個優先級,優先處理優先級高的事件,這樣就須要優先隊列。
       什麼是優先隊列?支持刪除最大元素和插入元素。javascript

二、怎樣實現?

       一、無序數組實現,在插入的時候往數組末尾插入,獲取的時候再獲取最大的,這樣插入的時間複雜度是O(1),獲取的時間複雜度是O(n)。
       二、有序數組實現,在插入的時候排序,獲取的時候獲取元素第一個,這樣插入時間複雜度是O(n),獲取的時間複雜度是O(1)。
       三、二叉堆實現。html

三、堆的定義

       數據結構二叉堆能很好的實現優先隊列的基本操做,在二叉堆數組中,每一個元素都要保證大於等於另外兩個特定位置的元素,相應的,這些位置的元素又至少要大於等於數組中另外兩個元素,以此類推。
       定義:當一顆二叉樹的每一個節點都大於等於它的兩個子節點的時候,它被稱爲堆有序。
       二叉堆表示法:若是用指針來表示,每一個元素都須要三個指針來找到它的上下結點(父節點和兩個子節點各須要一個)。可是若是咱們使用徹底二叉樹來表示,表達將會變得十分方便,徹底二叉樹只須要用數組而不須要用指針就能夠表示。
       定義:二叉堆是一組可以用徹底二叉樹排序的元素,而且在數組中按照層級存儲(不使用數組的第一個位置)。
       在一個堆中:位置爲k的父節點位置爲(k/2 須要向下取值),而它的兩個子節點的位置分別爲2K和2K+1,這樣在不使用指針的狀況下,咱們也能夠經過計算數組的索引在樹中上下移動。用數組(堆)實現的徹底二叉樹是很嚴格的,可是它的靈活性已經足以讓咱們高效的實現優先隊列。用它們咱們將能實現對數級別的插入元素和刪除最大的元素的操做。利用數組中無需指針便可沿着樹上下移動的便利以及如下性質,算法保證了對數複雜度的性能。java

四、堆的算法

       一、由下至上的堆有序化(上浮)若是堆的有序狀態由於某個結點變得比它的父結點更大而被打破。那麼咱們經過交換它和它的父節點來修復堆。
       二、由上至下的堆有序化(下沉) 若是堆的有序狀態由於某個結點變得比它的兩個子節點或者是其中之一更小而被打破了,那麼咱們能夠經過將它和它的兩個子結點中的較大者交換來恢復堆。
       三、插入元素:咱們將新添加的加到數組末尾,增長堆的大小而且讓這個新元素上浮到合適的位置。
       四、刪除最大元素:咱們從數組頂端刪去最大元素並將數組的最後一個元素放到頂端,減少堆的大小,而且讓這個元素下沉到合適的位置。
算法

五、總結

       優先隊列由一個基於堆的徹底二叉樹表示,存儲於數組pq[1··N]中,p[0]沒有使用,在insert中,咱們將N加一併把新元素添加在數組最後,而後用swim()恢復堆的秩序。在delMax()中咱們從pq1]中返回須要返回的元素,而後將pq[N]移動到pq[1],將N減一併用sink恢復堆的秩序,同時咱們還將再也不使用的pq[N+1]設置爲null,以便回收所佔用的空間。
       命題Q。對於一個含有N個元素的基於堆的優先隊列,插入元素操做只須要不過(lgN+1)次比較(上浮操做),刪除最大元素的操做須要不超過2lgN次比較。(下沉操做)。
       證實。由命題P可知,兩種操做都須要在根節點和堆底之間移動元素,而路徑長度不超過lgN,對於路徑上的每一個結點,刪除最大元素須要兩次比較(除了堆底元素),一次用來找出最大的子結點,一次用來確認子結點是否須要上浮
       對於須要大量混雜插入和刪除最大元素的操做的典型應用來講,命題Q意味着一個重要的性能突破。
       使用有序或者無序數組的優先隊列的初級實現老是須要線性時間來完成其中一種操做,但基於堆的實現可以保證在對數時間內完成他們,這種差異使得咱們解決之前沒法解決的問題。
數組

六、擴展

一、堆排序

       堆排序的基本思想是:將待排序序列構形成一個大頂堆,此時,整個序列的最大值就是堆頂的根節點。將其與末尾元素進行交換,此時末尾就爲最大值。而後將剩餘n-1個元素從新構形成一個堆,這樣會獲得n個元素的次小值。如此反覆執行,便能獲得一個有序序列了
堆排序實現原理?
       堆排序分爲兩個階段:第一階段實現一個堆,第二階段而後再下沉階段從堆中按照遞減順序取出全部元素並獲得排序結果。
N個元素構造一個堆的時間複雜度是多少?NlogN
       具體實現:咱們將數組直接做爲一個堆,而後對這個堆進行下沉操做。這樣作的好處是不須要額外空間了
              一、堆的構造:用swim上浮方法,須要從左到右掃描所有,用sink下沉方法,只須要掃描一半 如何利用下沉進行排序?
              一、從最後一個非葉子結點開始,從左至右,從下至上進行調整。這樣就能夠構成一個大碓頂
              二、將大碓頂元素和最後一個互換,而後再將剩下的進行下沉操做,等到只剩一個元素的時候下沉操做完成,排序也就完成了,由於每次下沉操做的複雜度是logn因此整體複雜度是Nlogn
數據結構

七、代碼實現

二叉堆實現

class MaxPQ{
    constructor(){
        //聲明一個數組保存二叉堆
        //0開頭啓用
        this.tp=[null];
    }
    size(){
        return this.tp.length;
    }
    isEmpty(){
        return this.tp.length===0;
    }
    //刪除二叉堆最大的元素(刪除頭部元素,而後最末尾的補上來,再下沉)
    delMax(){
        //獲取頭部元素,而且頭部元素置爲空
        var heade=this.tp[1];
        this.tp[1]=this.tp.pop();
        this.sink(1)
        console.log(this.tp);
        return heade;
        //出隊尾部
        //而後賦值給頭部,而後下沉
    }
    //插入(將新元素添加到數組末尾,增長堆的大小讓這個新元素上浮到合適的位置)
    insert(item){
        this.tp.push(item);
        this.swim(this.size()-1);
        console.log(this.tp);
    }
    //上浮
    swim(k){
        // k>1表明不是根節點而且根節點比子節點小
        while(k>1&&this.less(Math.floor(k/2),k)==-1){
            this.exch(Math.floor(k/2),k);
            k=Math.floor(k/2);
            console.log(k);
        }
    }
    //下沉
    sink(k){
        //若是是最後一個結點就不須要下沉了
        var j;
        while(2*k<=this.size()){
            //判斷
            j=2*k;
            //先比較子節點哪一個大,而後若是父節點比大的小,就交換
            //j小於N是爲了給另外結點留位置
            if(j<this.size()&&this.less(j,j+1)==-1) j++;
            //比對,若是k比j小就交換而後中止
            if(this.less(j,k)==1){
                this.exch(k,j);
                
            }else{
                break;
            }
            k=j;
            //沒有的話就把j賦值爲k
            
        }
    }
    //私有方法
    //i小於j 返回-1 i大於j返回1 i==j返回0
    less(i,j){
        if(this.tp[i]<this.tp[j]){
            return -1;
        }
        if(this.tp[i]>this.tp[j]){
            return 1;
        }
        if(this.tp[i]==this.tp[j]){
            return 0;
        }
    }

    //交換ij
    exch(i,j){
        var temp=this.tp[i];
        this.tp[i]=this.tp[j];
        this.tp[j]=temp;
    }
}
複製代碼

堆排序實現less

function sort(arrs) {
    //第一步獲取大頂堆(到這個的時候已經構建成爲了一個堆)
    //如何獲取大頂堆
    //此時咱們從最後一個非葉子結點開始,從左至右,從下至上進行調整。
    //這樣從最後一棵樹進行下沉操做每往上一個結點,就排好了一個堆,這樣就初步獲得了一個最大堆頂
    for(let k=Math.floor(arr.length/2);k>=1;k--){
        sink(arrs,k,arrs.length)
    }
    // console.log(arrs);
    var n=arr.length;
    while(n>1){
        exch(arrs,1,n--);
        sink(arrs,1,n);
    }
    console.log(arrs);
}


function sink(arrs, k, n) {
    //下沉步驟
    var j;
    //保證本身沒有降低到最後
    while (2 * k <= n-1) {
        //判斷
        j = 2 * k;
        //先比較子節點哪一個大,而後若是父節點比大的小,就交換
        //j小於N是爲了給另外結點留位置
        if (j < n-1 && less(j, j + 1) == -1) j++;
        //比對,若是k比j小就交換而後中止
        if (less(j, k) == 1) {
            exch(arrs,k, j);

        } else {
            break;
        }
        k = j;
        //沒有的話就把j賦值爲k

    }

    function less(i, j){
        if(arrs[i]<arrs[j]){
            return -1;
        }
        if(arrs[i]>arrs[j]){
            return 1;
        }
        if(arrs[i]==arrs[j]){
            return 0;
        }
    }
}
function exch(arrs, i, j) {
    var temp = arrs[i];
    arrs[i] = arrs[j];
    arrs[j] = temp;
}


let arr = [null,1, 6, 5, 8, 4, 2];

sort(arr)
複製代碼

參考

索引優先隊列的工做原理與簡易實現
圖解排序算法(三)之堆排序
算法第四版性能

相關文章
相關標籤/搜索