[資料結構與演算法]-二叉堆(binary heap)介紹及其實現(Java)
本文歡迎轉載,轉載前請聯絡作者。若未經允許轉載,轉載時請註明出處,謝謝! http://blog.csdn.net/colton_null 作者:喝酒不騎馬 Colton_Null from CSDN
一.什麼是二叉堆?
二叉堆(binary heap)是一種通常用於實現優先佇列的資料結構。要想知道二叉堆是什麼東西,得從兩方面介紹它:結構性和堆序性。
1.結構性
二叉堆是一顆除底層外被完全填滿的二叉樹,對於底層上的元素滿足從左到右填入的特性。如下圖所示。
所以,鑑於二叉堆的這個結構特性,我們可以很容易得出——一顆高為h的二叉堆有到 個節點。
而且,基於二叉堆的這個特性,我們可以用一個數組在表示這種資料結構,不需要使用鏈。如下圖所示。
陣列上任意位置i上的元素,它的左節點在2i上,右節點在2i + 1上,它的父節點在└i/2┘。不用連結串列的好處在於,這麼做對於計算機來說,訪問某一元素的效率很高,不需要遍歷即可訪問。
2.堆序性
這裡我們用最小堆(min-heap)來說明。
在堆中,每個節點n,都小於或等於它的兩個子節點。如下圖所示。
當然,我們可以類比處最大堆(max-heap)即每個節點都大於或等於它的子節點。
根據二叉堆的這個性質,最小元素或者最大元素即為根節點。我們直接就可以訪問到這個最小的元素,即array[1](陣列結構參考結構性)。
二.保持二叉堆的特性
在使用二叉堆的時候,我們對二叉堆插入和獲取最小值的操作,會破壞二叉堆的特性。那麼我們需要在每次操作之後,對二叉堆進行維護。
1.上濾
我們在執行插入(insert)操作的時候,可以採用上濾(percolate up)策略對二叉樹進行維護。
首先在插入元素t時,在底層建立一個空穴,以保證完全二叉樹的性質。如果元素t可以直接放在該空穴中不影響堆的堆序性,則插入完成;如果元素t不能直接放置在空穴中(即對於min-heap來說,t小於它的根節點或對於max-heap來說,t大於它的根節點),則把空穴的根節點放置在空穴中,原根節點被視為空穴。直到t被放入在合適的空穴中。
過程如下圖所示,插入元素18。
2.下濾
在我們獲取最小元素(即獲取根節點並刪除它時),我們需要用下濾(percolate down)策略來維護堆序性。
當刪除最小元素時,根節點變為空穴,那麼為了保證二叉堆的結構性,所以要把最後一個元素x移動到該堆的某個地方。如果x可以直接放入到空穴中,則下濾結束;否則,空穴子節點中最小或最大的值(這要看是min-heap還是max-heap,min-heap為最小值,max-heap為最大值)放入到空穴中,然後空穴下移一位,直到x可以被放置在空穴中。
過程如下圖所示。
3.最小二叉堆的Java實現
BinaryHeap.java
public class BinaryHeap<T extends Comparable<? super T>> {
public BinaryHeap() {
this(DEFAULT_CAPACITY);
}
public BinaryHeap(int capacity) {
currentSize = 0;
arr = (T[]) new Comparable[capacity + 1];
}
public BinaryHeap(T[] items) {
currentSize = items.length;
arr = (T[]) new Comparable[(currentSize + 2) * 11 / 10];
int i = 1;
for (T item : items) {
arr[i++] = item;
}
buildHeap();
}
/**
* 二叉堆的插入方法。使用上濾法。
*
* @param t 被插入的元素
*/
public void insert(T t) {
if (currentSize == arr.length - 1) {
// 如果當前元素個數為陣列長度-1,則擴容
enlargeArray(arr.length * 2 + 1);
}
int hole = ++currentSize;
// arr[0] = t初始化,最後如果迴圈到頂點,t.compartTo(arr[hole / 2])即arr[0]為0,迴圈結束
for (arr[0] = t; t.compareTo(arr[hole / 2]) < 0; hole /= 2) {
// 根節點的值賦值到子節點
arr[hole] = arr[hole / 2];
}
// 根節點(或樹葉節點)賦值為t
arr[hole] = t;
}
/**
* 尋找堆內最小值。索引1處的元素最小。
*
* @return
*/
public T findMin() {
if (isEmpty()) {
// 這裡如果堆為空,可以丟擲異常。
// throw new UnderflowException( );
}
// 第1位的元素最小
return arr[1];
}
public T deleteMin() {
if (isEmpty()) {
// 這裡如果堆為空,可以丟擲異常。
// throw new UnderflowException( );
}
T minItem = findMin();
// 將最後一個節點賦值到根節點
arr[1] = arr[currentSize--];
// 從根節點執行下濾
percolateDown(1);
return minItem;
}
/**
* 判斷堆是否為空
*
* @return 為空返回true;否則返回false
*/
public boolean isEmpty() {
return currentSize == 0;
}
/**
* 堆置空
*/
public void makeEmpty() {
currentSize = 0;
}
/**
* 列印堆
*/
public void print(){
System.out.print("堆為:");
for (int i = 1;arr[i] != null;i++){
System.out.print(arr[i] + " ");
}
System.out.println();
}
private static final int DEFAULT_CAPACITY = 10;
private int currentSize;
private T[] arr;
/**
* 下濾
*
* @param hole 下濾其實的節點的索引
*/
private void percolateDown(int hole) {
int child;
T tmp = arr[hole];
for (; hole * 2 <= currentSize; hole = child) {
child = hole * 2;
if (child != currentSize && arr[child + 1].compareTo(arr[child]) < 0) {
// 做子節點不為左後一個節點(說明有右節點)且右節點比做節點小,索引改為右節點節點
child++;
}
if (arr[child].compareTo(tmp) < 0) {
// 如果遍歷到的這個節點比最後一個元素小
arr[hole] = arr[child];
} else {
break;
}
}
// 將最後一個元素補到前面的空位
arr[hole] = tmp;
}
private void buildHeap() {
for (int i = currentSize / 2; i > 0; i--) {
percolateDown(i);
}
}
/**
* 擴容
* @param newSize 新陣列的大小
*/
private void enlargeArray(int newSize) {
T[] old = arr;
arr = (T[]) new Comparable[newSize];
for (int i = 0; i < old.length; i++) {
arr[i] = old[i];
}
}
}
BinaryHeapTest.java 測試類
public class BinaryHeapTest {
public static void main(String[] args) {
BinaryHeap binaryHeap = new BinaryHeap();
int[] nums = new int[]{23, 98, 34, 63, 3, 0, 87, 45};
for (Integer num : nums) {
binaryHeap.insert(num);
}
binaryHeap.print();
System.out.println("堆是否為空:" + binaryHeap.isEmpty());
System.out.println("獲取最小值:" + binaryHeap.deleteMin());
binaryHeap.print();
}
}
輸出:
堆為:0 23 3 45 63 34 87 98
堆是否為空:false
獲取最小值:0
堆為:3 23 34 45 63 98 87 98
最小二叉堆實現可用。
以上就是有關二叉堆(binary heap)介紹及其Java實現。
站在前人的肩膀上前行,感謝以下部落格及文獻的支援。
《資料結構與演算法分析(第3 版) 工業出版社》