數據結構之優先隊列--二叉堆(Java實現)

 前言

             數據結構隊列的學習中,我們知道隊列是先進先出的。任務被提交到隊列中,按照先進先出的原則

    對各個任務進行處理。不過在現實的情況下,任務通常有着優先級的概念,例如短任務、管理員的操作

    應該優先執行。這是使用隊列就無法描述了,這種特殊的應用我們使用一種稱之爲優先隊列(堆)的方式

    來解決。

 優先隊列

           和隊列一樣優先隊列也支持入隊和出隊操作,不過區別在於優先隊列的出隊操作出隊的是優先級最高

     的元素,這裏以元素的大小來判定任務的優先級,越小,優先級越高。優先隊列的模型如下圖:

          基於這種優先隊列的模型,我們可以多種方法對其進行實現,一種常用的方法就是使用鏈表以O(1)執

    行 插入操作,,遍歷最小元素並刪除花費O(N)時間。基於優先隊列的插入操作一般情況下多於刪除操作這

    一情況, 前者是一種較好的實現。

          另外可以使用二叉查找樹來實現,這樣一來插入和刪除操作都是O(logN),不過二叉樹支持多種

   操作用以實現優先隊列未免有點殺豬用牛刀的感覺。  下面將介紹二叉堆實現優先隊列。

  二叉堆

             二叉堆就結構性質上來說就是一個完全填滿的二叉樹,滿足結構性和堆序性。結構性不必多說,

     就是完全二叉樹應該滿足的樹結構。堆序性指的是:父節點的鍵值總是大於或等於(小於或等於)任何

     一個子節點的鍵值,且每個節點的左子樹和右子樹都是一個二叉堆(都是最大堆或最小堆)。

最大堆:當父節點的鍵值總是大於或等於任何一個子節點的鍵值。

              最小堆:當父節點的鍵值總是小於或等於任何一個子節點的鍵值。

                  上面圖片直接沿用的維基百科上的,筆者懶得在自己做個圖了。

      結構性質

               上面對二叉堆的結構性質略有提及,這裏我們進行下詳細說明。看看二叉堆是如何

         實現的。


                       仔細觀察上述完全二叉樹的結構,我們可以發現的是對於任意一個位置i,其

               結點的左孩子在2i的位置,右孩子在2i+1的位置上,基於上述的性質我們可以使用

               數組來實現二叉堆。

                    由此二叉堆可以使用一個數組和一個代表當前堆的大小的整數組成。考慮到涉及到

               元素大小的比較,該數組元素實現了Comparable接口。

public class BinaryHeap
{
    /**
     * Construct the binary heap.
     */
    public BinaryHeap( )
    {
        this( DEFAULT_CAPACITY );
    }
                                                                                                                                         
    /**
     * Construct the binary heap.
     * @param capacity the capacity of the binary heap.
     */
    public BinaryHeap( int capacity )
    {
        currentSize = 0;
        array = new Comparable[ capacity + 1 ];
    }
 private static final int DEFAULT_CAPACITY = 100;
                                                                                                                                         
    private int currentSize;      // Number of elements in heap
    private Comparable [ ] array; // The heap array
}


以上代碼顯示的是一個優先隊列的架構。至於其提供的具體操作,我們先看看  


二叉堆的堆序性。

堆序性

簡單的說保證優先隊列的刪除操作快速執行是堆序性質,基於這個特點我們需要找出最小元素,那麼最小元素應該在根結點上,也就是最小堆。這樣我們就能以常數時間執行findMin()操作了。

二叉堆基本操作

基於二叉堆的堆序性,我們來看看二叉堆基本操作是如何實現的吧!

insert(插入)

根據優先隊列的模型,二叉堆實現的優先隊列應該具備插入操作,不過根據其對序性具體的插入操作是如何進行的呢?看下面的演示:

                    二叉堆插入元素14的情況。



爲將一個元素 X 插入到堆中,我們在下一個可用位置創建一個空穴,否則該堆將不是完全數。

如果 X 可以放在該空穴中而不破壞堆的序,那麼插入完成。否則,我們把空穴的父節點上的元素

移入該空穴中,這樣,空穴就朝着根的方向上冒一步。繼續改過程直到 X 能被放入空穴中爲止。

這種實現過程稱之爲上濾新元素在堆中上濾直到找出正確的插入位置。

實現代碼:  

public void insert( Comparable x ) throws Overflow
        {
            //這裏沒有進行擴容處理
            if( isFull( ) )
                throw new Exception( );
                                                                                                                                     
                // Percolate up
            int hole = ++currentSize;
            //上濾,首先找到插入位置,之後元素交換一次
            for( ; hole > 1 && x.compareTo( array[ hole / 2 ] ) < 0; hole /= 2 )
                array[ hole ] = array[ hole / 2 ];
            array[ hole ] = x;
        }


可以知道的是當插入的元素小於堆中所有的元素的時候,必須上濾到根,插入時間爲O(logN)

deleteMin(刪除最小元)

基於優先隊列的模型,出隊的應該是最小元,按照最小堆的堆序性,找出最小元十分容易,麻煩的地方在於刪除之後破壞了結構型,這是需要進行一些額外的操作。當刪除一個最小元時,要在根節點建立一個空穴。由於現在堆少了一個元素,因此堆中最後一個元素 X 必須移動到該堆的某個地方。如果 X 可以直接被放到空穴中,那麼 deleteMin 完成。

不過這一般不太可能,因此我們將空穴的兩個兒子中比較小者移入空穴,這樣就把空穴向下推了一層。重複該步驟直到 X 可以被放入空穴中。因此,我們的做法是將 X 置入沿着從根開始包含最小兒子的一條路徑上的一個正確的位置。

演示過程如下:

首先我們刪除根元素13,建立一個空穴,之後判斷元素31是否可以放入這個空穴中,明顯不能,會破壞堆序性.

之後我們選擇較小的左兒子放入空穴,同時空穴下滑一層,之後判斷31是否置於空穴中


同上,26置於空穴中,空穴下滑一層,31可以置於空穴中,過程結束。這一種操作過程稱之爲下濾:空穴一步步下滑.

源碼實現:

public Comparable deleteMin( )
        {
            if( isEmpty( ) )
                return null;
                                                                                                                     
            Comparable minItem = findMin( );
            array[ 1 ] = array[ currentSize-- ];
            percolateDown( 1 );
                                                                                                                     
            return minItem;
        }
private void percolateDown( int hole )
        {
      int child;
      Comparable tmp = array[ hole ];
                                                                                                                     
      for( ; hole * 2 <= currentSize; hole = child )
            {
          child = hole * 2;
          if( child != currentSize &&
                 array[ child + 1 ].compareTo( array[ child ] ) < 0 )
              child++;
          if( array[ child ].compareTo( tmp ) < 0 )
              array[ hole ] = array[ child ];
                else
             break;
            }
         array[ hole ] = tmp;
        }

同樣的這個操作在最壞的情況下爲O(logN),平均而言也爲O(logN).

優先隊列其他操作

上面對二叉堆實現的優先隊列的兩個基本操作做了一些講解。我們知道的是在將任務提交給

優先隊列的時候有時候我們需要根據實際情況修改任務的優先級。

decreaseKey(降低關鍵字的值)

desreaseKey(p,m)操作降低在位置p處的值,降值幅度爲正m,不過這種方式很可能破壞堆序性,因此需要通過上濾操作進行調整。這種方式能夠動態的提高某個任務的優先級,使其在能夠優先開始。

increaseKey(降低關鍵字的值)

與上個操作相反,降低任務的優先級。  


delete(刪除)

                     刪除堆中某個任務,不過必須先執行decreasekey(P,+ ∞),然後執行deleteMin操作

                  這種操作的任務並不是正常終止的,而是被用戶終止的。

      構造二叉堆

                   上述講了二叉堆的方式實現優先隊列。那麼一個二叉堆又是如何構造的呢?

              簡單的我們可以認爲它可以使用N個相繼的insert操作來完成。每個insert最壞時間爲O(logN)

              則其構建時間爲O(N)。

                   更爲常用的算法是先保持其結構性,之後再通過檢查每個位置,下濾操作使其滿足堆序性。

一開始滿足結構性,但是並不滿足堆序性,我們在元素70的位置進行下濾操作。

代碼實現情況如下:        

public BinaryHeap( T[] items ){
    currentSize = items.length;
    array = (T[]) new Comparable[ (currentSize + 2) * 11 / 10 ];
                                                                                                           
    int i=1;
    for( T item : items ){
        array[ i++ ] = item;
    }
    buildHeap();
}
                                                                                                       
private void buildHeap(){
    for( int i = currentSize/2; i>0; i-- )
        percolateDown( i );
}

完整源碼:  

package com.kiritor;
                                                                                             
import java.util.Arrays;
                                                                                             
                                                                                             
public class BinaryHeap
{
                                                                                                 
    public BinaryHeap( )
    {
        this( DEFAULT_CAPACITY );
    }
    public BinaryHeap( Comparable[] items ){  
        currentSize = items.length;  
        array = new Comparable[ (currentSize + 2) * 11 / 10 ];  
                                                                                                       
        int i=1;  
        for( Comparable item : items ){  
            array[ i++ ] = item;  
        }  
        buildHeap();  
    }  
                                                                                                 
    public BinaryHeap( int capacity )
    {
        currentSize = 0;
        array = new Comparable[ capacity + 1 ];
    }
                                                                                             
    public void insert( Comparable x ) 
    {
                                                                                                     
            // Percolate up
        int hole = ++currentSize;
        for( ; hole > 1 && x.compareTo( array[ hole / 2 ] ) < 0; hole /= 2 )
            array[ hole ] = array[ hole / 2 ];
        array[ hole ] = x;
    }
                                                                                             
                                                                                               
    public Comparable findMin( )
    {
        if( isEmpty( ) )
            return null;
        return array[ 1 ];
    }
                                                                                             
                                                                                                
    public Comparable deleteMin( )
    {
        if( isEmpty( ) )
            return null;
                                                                                             
        Comparable minItem = findMin( );
        array[ 1 ] = array[ currentSize-- ];
        percolateDown( 1 );
                                                                                             
        return minItem;
    }
                                                                                             
                                                                                                
    private void buildHeap( )
    {
        for( int i = currentSize / 2; i > 0; i-- )
            percolateDown( i );
    }
                                                                                             
                                                                                                
    public boolean isEmpty( )
    {
        return currentSize == 0;
    }
                                                                                             
                                                                                                 
    public boolean isFull( )
    {
        return currentSize == array.length - 1;
    }
                                                                                             
                                                                                                
    public void makeEmpty( )
    {
        currentSize = 0;
    }
                                                                                             
    private static final int DEFAULT_CAPACITY = 100;
                                                                                             
    private int currentSize;      // Number of elements in heap
    private Comparable [ ] array; // The heap array
                                                                                             
                                                                                                
    private void percolateDown( int hole )
    {
      int child;
     Comparable tmp = array[ hole ];
                                                                                             
     for( ; hole * 2 <= currentSize; hole = child )
        {
          child = hole * 2;
          if( child != currentSize &&
                 array[ child + 1 ].compareTo( array[ child ] ) < 0 )
              child++;
          if( array[ child ].compareTo( tmp ) < 0 )
              array[ hole ] = array[ child ];
            else
              break;
        }
      array[ hole ] = tmp;
    }
                                                                                             
        // Test program
    public static void main( String [ ] args )
    {
        int numItems = 50;
        BinaryHeap h = new BinaryHeap( numItems );
        int i = 37;
                                                                                             
        try
        {
            for( i = 37; i != 0; i = ( i + 37 ) % numItems )
                h.insert( new Integer( i ) );
             System.out.println(Arrays.toString(h.array));
             System.out.println(h.findMin());
             h.deleteMin();
             System.out.println(Arrays.toString(h.array));
        }
        catch( Exception e )
          { System.out.println( "Overflow (expected)! " + i  ); }
    }
}

簡單執行結果:  

[null, 1, 2, 7, 6, 3, 12, 10, 9, 8, 5, 4, 13, 24, 22, 11, 34, 21, 19, 16, 27, 17, 15, 14, 25, 38, 31, 49, 36, 23, 18, 47, 48, 35, 42, 45, 46, 32, 29, 43, 40, 30, 37, 41, 33, 28, 20, 39, 44, 26, null]
1
[null, 2, 3, 7, 6, 4, 12, 10, 9, 8, 5, 14, 13, 24, 22, 11, 34, 21, 19, 16, 27, 17, 15, 20, 25, 38, 31, 49, 36, 23, 18, 47, 48, 35, 42, 45, 46, 32, 29, 43, 40, 30, 37, 41, 33, 28, 26, 39, 44, 26, null]


                                                                                                                             By      Kiritor


                                                                                                                             2013 /06 /16   父親節




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