算法系列筆記(七)優先隊列

優先隊列是一種抽象的數據類型,表示一組值和對這些值的操作,其中最重要的操作就是刪除最大元素和插入元素。

二叉堆數組中,每個元素都要保證大于等于另兩個特定位置的元素,相應這些位置的元素又至少要大于等于數組中的另兩個的元素。

如果將所有元素弄成一個二叉樹,靈魂畫手就覺得此事很簡單:

二叉樹.png

這是一個二叉樹,其中當它每一個節點都大于等于它的兩個子節點時,稱之為堆有序。

比如C D一定小于B B,E一定小于A。但E和B的大小比較不一定,可能E比D還小也說不定。唯一可確定的是,樹的根節點,A一定是最大的元素。

二叉堆表示法:

如果我們用指針來表示堆有序的二叉樹,那么每個元素都需要三個指針來找到它的上下結點,雖然可能不會全用上。如果我們采用完全二叉樹complete tree
(完全二叉樹從根結點到倒數第二層滿足完美二叉樹(完美就是都是有兩個子結點,而且高度填充一樣),最后一層可以不完全填充,其葉子結點都靠左對齊。)
甚至可以用數組直接表達,具體是層級表示,比如根節點在序號1,子節點則在序號2和3,以此類推(并不用序號0)

二叉堆就是一組堆有序的完全二叉樹排序的元素,在數組中按照層次存儲。一棵大小為N的完全二叉樹的高度為log2N下取整(只有一個父結點高度認為為0)

這些元素在數組中用層次存儲是有規律的,二叉堆中,位置k的結點的父結點的位置在(k/2下取整),它的兩個子結點的位置為2k和2k+1。

上浮實現堆有序化

如果堆有序狀態因為某個結點變得比它的父結點更大而被打破,這樣就要不斷交換它和它的父結點來修復有序狀態。這種就好像是小弟立了功就被人提拔上去的感覺。

private void swim(int k){
  while(k>1&&(k/2)<k){
       exchange(a,k,k/2);
       k=k/2; 
 }
}
下沉實現堆有序化

父結點比子結點的數小,所以父結點要和它的子結點交換,直到它沒有子結點或者比子結點大為止。差不多就是上司犯錯然后被降級的感覺

    private void sink(int k) {
        while(2*k<=n) {
            int j=2*k;
            if(j<n&&a[j]<a[j+1])j++;  //找到兩個子結點中較大的那個
            if(a[k]>=a[j])break;
            exchange(k, j);
            k=j;
        }
    }

根據上面的sink和swim就可以實現對一個堆有序序列進行元素的增刪,并維護它的順序

public class MaxPQ {
    private int n=0;
    private int[] a;
    public MaxPQ(int maxlength) {
        a=new int[maxlength];
    }
    public int size() {
        return n;
    }
    public void insert(int v) {
        a[++n]=v;
        swim(n);
    }
    public int delmax() {        //這個很帥,將最大的拿出來讓最底層的結點放上面來然后下沉排序
        int max=a[1];
        exchange(1, n--);
        sink(1);
        return max;
    }
    private void swim(int k){
        while(k>1&&(k/2)<k){
           exchange(k,k/2);
           k=k/2; 
        }
    }
    private void sink(int k) {
        while(2*k<=n) {
            int j=2*k;
            if(j<n&&a[j]<a[j+1])j++;
            if(a[k]>=a[j])break;
            exchange(k, j);
            k=j;
        }
    }
    
    
    private  void exchange(int i,int j) {
        int temp=a[i];
        a[i]=a[j];
        a[j]=temp;
    }
}

對于這個有幾點改進

  1. 這個可以用不定容數組代替定容數組
  2. 可以通過模擬多叉樹來實現多叉堆,比如三叉堆就是位置k的元素有3k-1、3k、3k+1三個結點。同理修改d叉樹也不是什么難事,我們需要在樹高和d個子結點找到折中。
  3. 添加索引,方便從大到小遍歷元素

堆排序

堆排序分成兩個階段,構造堆和下沉排序階段。

堆的構造分成兩種:一個是通過數組的從左往右遍歷數組調用swim,一種是從右往左遍歷數組調用sink方法。聽說好像后面那種更加聰明。

有一種堆排序的實現方式如下,開始時掃描數組一半的元素用sink方法,因為我們可以跳過大小為一的子堆,因為n/2-n這些元素都是沒有子結點的結點。
這里sink方法改了一下方法參數,但內容是一樣的

public static void sort(int [] a){
          int n=a.length();
          for(int k=n/2;k>=1;k--){
              sink(a,k,n);
}
//上面方法結束后保證堆有序了

//下沉排序
  while(n>1){
    exch(a,1,n--);  //交換位置且縮小堆
    sink(a,1,n);
}
}

下沉排序主要工作是在第二階段完成的。這里我們將堆中最大元素刪了,放在縮小堆后空出來的位置(其實是交換了元素位置再縮小堆)。然后再sink

堆排序是唯一一個能同時最優利用時間和空間的排序。在最壞的情況下也能保證2Nlogn的比較和恒定額外的空間。但是它很少利用緩存(啥意思?)

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 牛在遠古時代中特別是在牛耕時代,牛占主導地位,現如今科技空前發展,很多地方都不用牛而改用機器,但部分地區因受到地里...
    南林瑤閱讀 484評論 0 2
  • 不言簡書閱讀 220評論 1 0
  • 互聯網上面現在有很多很多的免費資源、免費微課,曾何幾時我的朋友圈經常就是一句“報名參加”后面附上聽課鏈接,然后截圖...
    Jeudi閱讀 392評論 0 2
  • 你厚重的腳步 踩碎時光的年輪 擔一路風雨 從歷史的深處走來 無語如碑文 默默佇立在風塵 黃河水滾滾流淌 太陽神賦了...
    田園執筆閱讀 122評論 0 1