[算法系列]優先隊列,堆與堆排序

優先隊列,堆與堆排序

1 優先隊列

有時我們在處理有序元素時,並不一定要求他們全部有序. 很多情況下我們會收集一些元素, 處理當前最大的元素, 然後再收集更多元素, 再處理當前最大元素 …

這種情況下, 一個合適的數據結構一個支持兩種操作 : 刪除最大元素插入元素. 這種數據類型叫做 優先隊列.

我們可以使用有序或無序的數組或鏈表實現.使用無序序列是解決這個問題的 惰性方法, 我們僅在必要的時候纔會找出最大元素(比如在pop的時候).

而使用有序序列是解決問題的積極方法, 在插入insert元素時就保持列表有序.

對於上述的對優先隊列的所有實現中 , 插入元素和刪除最大元素這兩個操作之一在最壞情況下需要線性時間完成:

  • 有序數組插入元素是O(n)的, 刪除是O(1)
  • 無序數組插入元素是O(1)的, 刪除是O(n)

而二叉堆能保證這兩種操作能夠更快地運行.

2 堆的定義與算法

2.1定義

二叉堆能夠很好地實現優先隊列的基本操作. 在二叉堆數組中,每個元素都要大於等於另外兩個特定位置的元素.(默認大頂堆)

  • 定義堆有序: 當一個二叉樹的每個結點都大於等於它的兩個子結點時, 被稱爲堆有序.

所以 不難得出根結點是堆有序的二叉樹中的最大結點

2.2 二叉堆表示法

  • 定義二叉堆是一組能夠用堆有序的完全二叉樹排序的元素, 並在數組中按層級儲存(第一個位置不用)

以下二叉堆簡稱 . 在一個堆中 , 位置k的 結點的父節點位置爲 k/2 (整型類型, 小數不保留, 比如 5 的父節點在 2), 而它的兩個子節點的位置分別在 2k 和 2k+1.

由此我們可以通過計算數組的索引在樹中上下移動: a[k]向上一層就 k = k /2 ; 向左子樹就 k = 2k 或 2k +1

在這裏插入圖片描述在這裏插入圖片描述

2.3堆的一些算法

我們用長度爲N + 1 的數組來保存一個大小爲N的堆, 我們不使用a[0] , 元素全放在a[1] ~ a[N]中

  1. 輔助函數less() 和 exch()
/* a 爲實現了Comparable的類型的堆 , i和j爲數組下標 */   	
/* 交換下標爲 i , j的兩個元素 */
void exch(Comparable[] a , int i , int j ){
	Comparable temp = a[i]; a[i] = a[j]; a[j] = temp;
}
/*判斷下標爲i的元素是否小於下標爲j的函數*/
boolean less(Comparable[] a , int i , int j){
	return (a[i].compareTo(a[j]) < 0) ;
}
  1. 由下至上的堆有序swim( )

    若某結點比他的父節點還要大, 則他需要通過交換他和他的父節點來修復堆. 但交換後的父節點可能比父節點的父節點還大 , 所以還得一遍遍地向上恢復.

        private void swin (Comparable[] a , int k , int N){ //N 初始表示最後一個元素下標 N = a.length - 1 , 也是堆中實際元素個數
            while (k > 1 && less(a , k /2 , k)){
                exch(a , k/2 , k);
                k /= 2;
            }
        }
    

在這裏插入圖片描述

  1. 由下至上的堆有序化sink( )

    如果某結點比他的兩個子節點或者其中之一還要小 , 則需要通過將他和他的子節點中的較大值進行交換. 但交換後依然可能比子節點的子節點還小, 所以也得一遍遍地向下恢復

        private void sink(Comparable[] a , int k , int N){		
            /* 向下遍歷, 但是隻在前一半元素中考慮 */
            while ( 2 * k <= N){
                int j = 2 * k ;
                /* 如果他的右孩子比他大 , 而且也比左孩子大 , 則選擇與右孩子進行交換 */
                if(  j < N  && less(a , j, j + 1)) j ++;
                /* 如果此時k已經不小於他的孩子了 , 跳出循環 */
                if( ! less(a ,k  , j )) break;
                /* 進行交換 */
                exch(a , k , j);
                k = j;
            }
        }
    

在這裏插入圖片描述

  1. 向堆中插入一個元素v , 並且調整堆保持堆有序

    public void intsert(Comparable[] a , Comparable v , int N){
    	a[++N] = v;
    	swim(N);
    }
    
  2. 刪除堆中的最大元素,調整堆保持其有序

    public Comparable delMax(Comparable[] a , int N ){
        /* 得到最大元素 */
        Comparable max = a[1];
        /* 和最後一個元素進行交換, 交換完後將N減1 */
        exch(a , 1 , N -- );
        a[N + 1] = null;
        sink(a , 1 , a.length)
    }
    

2.4 堆排序

在上述構造了堆的數據結構後 , 堆排序就比較簡單了.

堆排序主要是兩個步驟:

  • 將一個無序的數組a (還是默認a[0]不存值 )變得堆有序(成爲一個堆) – 構造堆

  • 將該最大元素與最後一個元素交換(交換後堆頂爲最小元素) , 同時用sink將最小元素下沉, 用while對每個元素進行該步驟 – 交換+下沉排序

    public void heapSort(Comparable[] a){
    	int N = a.length - 1;
        /* 對前一半的元素進行sink下沉 , 得到一個堆 */
    	for (int k = N / 2 ; K >= 1 ; k --)
    		sink(a , k , N );
        /* 對每個元素進行操作 */
    	while( N > 1){
            /* 交換最大的元素與最後一個元素 , 此時最大元素跑到a[N] , 然後N-- , 表示當前a[N]排序完成, */
    		exch(a , 1 , N --);
            /* 上面的N--相當於從堆中拿出了排好的元素  , 現在把最小的元素沉下去, 當前堆頂又是當前最大值了 */
    		sink(a , 1 , N );
    	}
    }
    

在這裏插入圖片描述
附堆排序實現:

   public  void heapSort(Comparable[] a){
       int N  = a .length -1 ;
       for(int k =  N /2 ; k >= 1 ; k --)
           sink(a , k , N);
       while ( N > 1){
           exch(a , 1  ,N -- );
           sink(a , 1  ,N  );
       }
   }

     void exch(Comparable[] a , int i , int j ){
       Comparable temp = a[i]; a[i] = a[j]; a[j] = temp;
   }

    boolean less(Comparable[] a , int i , int j){
       return (a[i].compareTo(a[j]) < 0) ;
   }

   private void sink(Comparable[] a , int k , int N){
       while ( 2 * k <= N){
           int j = 2 * k ;
           if(  j < N  && less(a , j, j + 1)) j ++;
           if( ! less(a ,k  , j )) break;
           exch(a , k , j);
           k = j;
       }
   }

   private void swin (Comparable[] a , int k , int N){
       while (k > 1 && less(a , k /2 , k)){
           exch(a , k/2 , k);
           k /= 2;
       }
   }

   @Test
   public void test(){

       Integer[] a = {null , 33 ,5,78,23,4 , 354 ,78,114, 6};
       for (int i  = 1; i < a .length ; i ++ ) {
           System.out.print( a[i] +  " ");
       }
       System.out.println();

       heapSort(a);

       for (int i  = 1; i < a .length ; i ++ ) {
           System.out.print( a[i] +  " ");
       }
   }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章