数据结构之优先队列--二叉堆(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 heapprivate Comparable [ ] array; // The heap array }以上代碼顯示的是一個優先隊列的架構。至于其提供的具體操作,我們先看看 ?
二叉堆的堆序性。
堆序性
簡單的說保證優先隊列的刪除操作快速執行是堆序性質,基于這個特點我們需要找出最小元素,那么最小元素應該在根結點上,也就是最小堆。這樣我們就能以常數時間執行findMin()操作了。
二叉堆基本操作
基于二叉堆的堆序性,我們來看看二叉堆基本操作是如何實現的吧!
insert(插入)
根據優先隊列的模型,二叉堆實現的優先隊列應該具備插入操作,不過根據其對序性具體的插入操作是如何進行的呢?看下面的演示:
? ? ? ? ? ? ? ? ? ? 二叉堆插入元素14的情況。
為將一個元素 X 插入到堆中,我們在下一個可用位置創建一個空穴,否則該堆將不是完全數。
如果 X 可以放在該空穴中而不破壞堆的序,那么插入完成。否則,我們把空穴的父節點上的元素
移入該空穴中,這樣,空穴就朝著根的方向上冒一步。繼續改過程直到 X 能被放入空穴中為止。
這種實現過程稱之為上濾:新元素在堆中上濾直到找出正確的插入位置。
實現代碼: ?
public void insert( Comparable x ) throws Overflow{//這里沒有進行擴容處理if( isFull( ) )throw new Exception( );// Percolate upint 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 ];elsebreak;}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 upint 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 heapprivate Comparable [ ] array; // The heap arrayprivate 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 ];elsebreak;}array[ hole ] = tmp;}// Test programpublic 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 ? 父親節
轉載于:https://blog.51cto.com/kiritor/1226661
總結
以上是生活随笔為你收集整理的数据结构之优先队列--二叉堆(Java实现)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Django REST framewor
- 下一篇: Git入门到删库