優先隊列之二叉堆(JAVA實現)

一、定義:

1.完全二叉樹(除了最後一層可能不飽和,其他都飽和,且最後一層節點是從左往右排滿)
2.堆性:父節點要小於等於(最小堆)或者大於等於(最大堆)子節點。

這裏寫圖片描述

二叉堆由於是完全二叉樹,故父節點和子節點的位置存在一定的關係。若將二叉堆的第一個元素放在數組索引爲1的位置,父節點和子節點的位置關係如下:
 1. 索引爲i的左孩子的索引是 (2*i);
 2. 索引爲i的左孩子的索引是 (2*i+1);
 3. 索引爲i的父結點的索引是 (i/2)
故我們一般通過數組來實現二叉堆。

通過上述定義可知,“最大堆”和“最小堆”是對稱關係。本文以最小堆爲例進行描述。

二、實現思路:

1.插入(上濾)

  • a.爲將一個元素 X 插入到堆中,我們在下一個可用位置創建一個空穴(初始爲堆的末尾,結構性)。
  • b. 如果 X 可以放在該空穴中而不破壞堆的序,那麼插入完成。
  • c.如果不能,我們把空穴的父節點上的元素移入該空穴中,這樣,空穴就朝着根的方向上冒一步。
  • d.繼續b,c的過程直到 X 能被放入空穴中爲止。

    如下圖:演示將元素14插入二叉堆的過程
    這裏寫圖片描述

這裏寫圖片描述
2.刪除根元素(最小值/最大值)(下濾)

  • a.將根節點刪除,使之成爲空穴(由於現在堆少了一個元素,因此堆中最後一個元素 X 必須移動到該堆的某個地方,結構性)。
  • b.如果 X 可以直接被放到空穴中,那麼 deleteMin 完成。
  • c.如果不可以,將空穴的兩個兒子中比較小者移入空穴,這樣就把空穴向下推了一層。
  • d.重複b,c 直到 X 可以被放入空穴中。

    如下圖:演示將根元素13刪除的過程
    這裏寫圖片描述
    這裏寫圖片描述

3.創建:
a.簡單的我們可以認爲它可以使用N個相繼的insert操作來完成。每個insert最壞時間爲O(logN),則其構建時間爲O(N)。
b.更爲常用的算法是先保持其結構性,之後再通過檢查每個位置,下濾操作使其滿足堆序性。如下:

一開始滿足結構性,但是並不滿足堆序性,我們在元素70的位置進行下濾操作。

三、代碼實現

import java.util.ArrayList;

/**
 * 頭元素存儲於1位置
 * i節點的左二子位置2*i 右兒子位置2*i+1   父親位置i/2
 */
public class BinaryHeap<T extends Comparable<T>> {
    /**
     * 由於java禁止使用泛型數組,故此處使用ArrayList存儲信息
     */
    private ArrayList<T> array;//
    private int currentSize;//大小


    public BinaryHeap() {
        array = new ArrayList<>();
        array.add(null);
    }

    /**
     * 將數組轉化爲二叉堆
     *
     * @param array
     */
    public BinaryHeap(ArrayList<T> array) {
        currentSize = array.size();
        int i = 1;
        for (T t : array) {//保證結構性
            this.array.set(i++, t);
        }
        //保證堆性 下濾
        for (i = currentSize / 2; i > 0; i--) {
            percolateDown(i);
        }
    }

    /**
     * 插入
     *
     * @param x
     */
    public void insert(T x) {
        int hole = currentSize + 1;//空穴的初始位置
        array.add(x);
        //當x元素小於空穴的父節點時,空穴進行上濾
        for (; hole > 1 && x.compareTo(array.get(hole / 2)) < 0; hole = hole / 2) {
            array.set(hole, array.get(hole / 2));
        }
        //當x元素不小於空穴的父節點元素時,找到合適的位置,放入
        array.set(hole, x);
        currentSize++;
    }

    /**
     * 查找最小元素
     *
     * @return
     */
    public T findMin() {
        return array.get(1);
    }

    /**
     * 刪除最小元素
     *
     * @return
     */
    public T deleteMin() {
        if (currentSize < 1) {
            System.out.println("BinaryHeap is Empty");
        }
        T minElement = array.get(1);//獲取最小元素
        array.set(1, array.get(currentSize));//將末尾元素存入
        array.remove(currentSize--);//移除最後元素,並使大小-1
        if (currentSize > 0) {
            percolateDown(1);//下濾
        }
        return minElement;
    }

    /**
     * 刪除任一元素
     *
     * @return 元素不存在,返回-1
     * 刪除成功,返回該元素的下標
     */
    public int delete(T x) throws Exception {
        if (currentSize < 1) {
            throw new Exception("BinaryHeap is Empty");
        }
        int index = array.indexOf(x);//獲取x的索引
        if (index == -1) {
            return -1;
        }
        array.set(index, array.get(currentSize));
        array.remove(currentSize--);
        percolateDown(index);//下濾
        return index;
    }

    /**
     * 下濾
     */
    public void percolateDown(int hole) {
        int child;
        T temp = array.get(hole);//需要下濾的元素,臨時存儲
        for (; hole * 2 <= currentSize; hole = child) {
            child = hole * 2;
            //child不爲最後一個元素,且右元素小於左元素時:child爲右元素,否則child爲左元素
            if (child < currentSize && array.get(child).compareTo(array.get(child + 1)) > 0) {
                child++;
            }
            if (temp.compareTo(array.get(child)) > 0) {//temp大於較小的元素 ,將空穴下濾一層
                array.set(hole, array.get(child));
            } else {
                break;//找到合適的位置,跳出循環替換
            }
        }
        array.set(hole, temp);
    }

    public static void main(String[] args) {
        int numItems = 1000;
        BinaryHeap<Integer> h = new BinaryHeap<>();
        int i = 37;
        for (i = 37; i != 0; i = (i + 37) % numItems) {
            h.insert(i);
        }

        for (i = 1; i < numItems; i++) {
            if (h.deleteMin() != i)
                System.out.println("Oops! " + i);
        }

    }
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章