數據結構隊列的學習中,咱們知道隊列是先進先出的。任務被提交到隊列中,按照先進先出的原則java
對各個任務進行處理。不過在現實的狀況下,任務一般有着優先級的概念,例如短任務、管理員的操做算法
應該優先執行。這是使用隊列就沒法描述了,這種特殊的應用咱們使用一種稱之爲優先隊列(堆)的方式數組
來解決。數據結構
和隊列同樣優先隊列也支持入隊和出隊操做,不過區別在於優先隊列的出隊操做出隊的是優先級最高架構
的元素,這裏以元素的大小來斷定任務的優先級,越小,優先級越高。優先隊列的模型以下圖:ide
基於這種優先隊列的模型,咱們能夠多種方法對其進行實現,一種經常使用的方法就是使用鏈表以O(1)執學習
行 插入操做,,遍歷最小元素並刪除花費O(N)時間。基於優先隊列的插入操做通常狀況下多於刪除操做這ui
一狀況, 前者是一種較好的實現。this
另外可使用二叉查找樹來實現,這樣一來插入和刪除操做都是O(logN),不過二叉樹支持多種spa
操做用以實現優先隊列未免有點殺豬用牛刀的感受。 下面將介紹二叉堆實現優先隊列。
二叉堆就結構性質上來講就是一個徹底填滿的二叉樹,知足結構性和堆序性。結構性沒必要多說,
就是徹底二叉樹應該知足的樹結構。堆序性指的是:父節點的鍵值老是大於或等於(小於或等於)任何
一個子節點的鍵值,且每一個節點的左子樹和右子樹都是一個二叉堆(都是最大堆或最小堆)。
最大堆:當父節點的鍵值老是大於或等於任何一個子節點的鍵值。
最小堆:當父節點的鍵值老是小於或等於任何一個子節點的鍵值。
上面圖片直接沿用的維基百科上的,筆者懶得在本身作個圖了。
上面對二叉堆的結構性質略有說起,這裏咱們進行下詳細說明。看看二叉堆是如何
實現的。
仔細觀察上述徹底二叉樹的結構,咱們能夠發現的是對於任意一個位置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()操做了。
基於二叉堆的堆序性,咱們來看看二叉堆基本操做是如何實現的吧!
根據優先隊列的模型,二叉堆實現的優先隊列應該具有插入操做,不過根據其對序性具體的插入操做是如何進行的呢?看下面的演示:
二叉堆插入元素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)
基於優先隊列的模型,出隊的應該是最小元,按照最小堆的堆序性,找出最小元十分容易,麻煩的地方在於刪除以後破壞告終構型,這是須要進行一些額外的操做。當刪除一個最小元時,要在根節點創建一個空穴。因爲如今堆少了一個元素,所以堆中最後一個元素 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).
上面對二叉堆實現的優先隊列的兩個基本操做作了一些講解。咱們知道的是在將任務提交給
優先隊列的時候有時候咱們須要根據實際狀況修改任務的優先級。
desreaseKey(p,m)操做下降在位置p處的值,降值幅度爲正m,不過這種方式極可能破壞堆序性,所以須要經過上濾操做進行調整。這種方式可以動態的提升某個任務的優先級,使其在可以優先開始。
與上個操做相反,下降任務的優先級。
刪除堆中某個任務,不過必須先執行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 父親節