優先隊列的實現常選用二叉堆,在數據結構中,優先隊列通常也是指堆。java
堆的兩個性質:數組
結構性:堆是一顆除底層外被徹底填滿的二叉樹,底層的節點從左到右填入,這樣的樹叫作徹底二叉樹。數據結構
堆序性:因爲咱們想很快找出最小元,則最小元應該在根上,任意節點都小於它的後裔,這就是小頂堆(Min-Heap);若是是查找最大元,則最大元應該在根上,任意節點都要大於它的後裔,這就是大頂堆(Max-heap)。app
經過觀察發現,徹底二叉樹能夠直接使用一個數組表示而不須要使用其餘數據結構。因此咱們只須要傳入一個size就能夠構建優先隊列的結構(元素之間使用compareTo方法進行比較)。測試
public class PriorityQueue<T extends Comparable<? super T>> {
public PriorityQueue(int capacity) {
currentSize = 0;
array = (T[]) new Comparable[capacity + 1];
}
}
對於數組中的任意位置 i 的元素,其左兒子在位置 2i 上,則右兒子在 2i+1 上,父節點在 在 i/2(向下取整)上。一般從數組下標1開始存儲,這樣的好處在於很方便找到左右、及父節點。若是從0開始,左兒子在2i+1,右兒子在2i+2,父節點在(i-1)/2(向下取整)。ui
咱們這創建最小堆,即對於每個元素X,X的父親中的關鍵字小於(或等於)X中的關鍵字,根節點除外(它沒有父節點)。spa
如圖所示,只有左邊是堆,右邊紅色節點違反堆序性。根據堆序性,只須要常O(1)找到最小元。3d
圖中演示了18插入的過程,在下一個可用的位置創建空穴(知足結構性),發現不能直接插入,將父節點移下來,空穴上冒。繼續這個過程,直到知足堆序性。這樣就實現了元素插入到優先隊列(堆)中。code
/**
* 插入到優先隊列,維護堆序性
*
* @param x :插入的元素
*/
public void insert(T x) {
if (null == x) {
return;
}
//擴容
if (currentSize == array.length - 1) {
enlargeArray(array.length * 2 + 1);
}
//上濾
int hole = ++currentSize;
for (array[0] = x; x.compareTo(array[hole / 2]) < 0; hole /= 2) {
array[hole] = array[hole / 2];
}
array[hole] = x;
}
/**
* 擴容方法
*
* @param newSize :擴容後的容量,爲原來的2倍+1
*/
private void enlargeArray(int newSize) {
T[] old = array;
array = (T[]) new Comparable[newSize];
System.arraycopy(old, 0, array, 0, old.length);
}
能夠反覆使用交換操做來進行上濾過程,但若是插入X上濾d層,則須要3d次賦值;咱們這種方式只須要d+1次賦值。orm
若是插入的元素是新的最小元從而一直上濾到根處,那麼這種插入的時間長達O(logN)。但平均來看,上濾終止得要早。業已證實,執行依次插入平均須要2.607次比較,所以平均insert操做上移元素1.607層。上濾次數只比插入次數少一次。
如圖所示:在根處創建空穴,將最後一個元素放到空穴,已知足結構性;爲知足堆序性,須要將空穴下移到合適的位置。
注意:堆的實現中,常常發生的錯誤是只有偶數個元素,即有一個節點只有一個兒子。因此須要測試右兒子的存在性。
/**
* 刪除最小元
* 若優先隊列爲空,拋出UnderflowException
*
* @return :返回最小元
*/
public T deleteMin() {
if (isEmpty()) {
throw new UnderflowException();
}
T minItem = findMin();
array[1] = array[currentSize--];
percolateDown(1);
return minItem;
}
/**
* 下濾方法
*
* @param hole :從數組下標hole1開始下濾
*/
private void percolateDown(int hole) {
int child;
T 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)。
優先隊列常使用二叉堆實現,本篇圖解了二叉堆最基本的兩個操做:插入及刪除最小元。insert以O(1)常數時間執行,deleteMin以O(logN)執行。相信你們看了以後就能夠去看java的PriorityQueue源碼了。今天只說了二叉堆最基本的操做,還有一些額外操做及分析下次再說。好比,如何證實buildHeap是線性的?以及優先隊列的應用等。
聲明:圖文皆原創,若有轉載,請註明出處。若有錯誤,請幫忙指出,歡迎討論;若以爲能夠,點下推薦支持支持。