1. 程式人生 > >[資料結構與演算法]-二叉堆(binary heap)介紹及其實現(Java)

[資料結構與演算法]-二叉堆(binary heap)介紹及其實現(Java)

本文歡迎轉載,轉載前請聯絡作者。若未經允許轉載,轉載時請註明出處,謝謝! http://blog.csdn.net/colton_null 作者:喝酒不騎馬 Colton_Null from CSDN

一.什麼是二叉堆?

二叉堆(binary heap)是一種通常用於實現優先佇列的資料結構。要想知道二叉堆是什麼東西,得從兩方面介紹它:結構性和堆序性。

1.結構性

二叉堆是一顆除底層外被完全填滿的二叉樹,對於底層上的元素滿足從左到右填入的特性。如下圖所示。
這裡寫圖片描述
所以,鑑於二叉堆的這個結構特性,我們可以很容易得出——一顆高為h的二叉堆有2h2h+11 個節點。

而且,基於二叉堆的這個特性,我們可以用一個數組在表示這種資料結構,不需要使用鏈。如下圖所示。
這裡寫圖片描述

陣列上任意位置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 版) 工業出版社》