Java底層實現PriorityQueue 優先隊列

PriorityQueue 優先隊列

基於MaxHeap最大堆


1、什麼是優先隊列

  優先隊列也是一種隊列,它的接口函數和隊列相同。

public interface Queue<E> {
    int getSize();
    boolean isEmpty();
    E dequeue();
    void enqueue(E e);
    E getFront();
}

  雖然代碼相同,需要注意的是,出隊操作:拿出最大值(優先級最高)。但相對於普通的隊列有着宏觀上的不同。

  • 普通隊列:先進先出,後進後出
  • 優先隊列:出隊和入隊得順序無關,和優先級有關

  形象地理解就是超市和醫院的排隊。超市排隊這種特性就符合普通隊列的形式。先排隊先結賬。醫院就不一樣啦,醫院要優先處理急診的病人,這就跟優先級有關,優先級越高的元素放在最前面。優先進行處理。

不同的底層實現方法:

  作爲一種抽象的數據結構,底層實現的方法包含很多。數組或者鏈表這種線性結構,當然還有我們的樹結構,當然我們也可以引入順序的線性結構。具體的複雜度如下:

入隊 出隊
普通線性結構 O(1) O(N):拿出最大元素
順序線性結構 O(N):順序放入元素 O(1)
二叉堆 O(log(N)) O(log(N))

2、二叉堆的實現

2.1、什麼是二叉堆

  二叉堆是一個完全二叉樹。那什麼是完全二叉樹呢?
  滿二叉樹就是除了最下面一層,其他的節點都是具有左右孩子節點,就類似於這樣。

滿二叉樹

完全二叉樹就類似這種:

完全二叉樹

  完全二叉樹不是一顆滿的二叉樹,但是它的不滿的那一部分一定在他的右下角部分。存放的過程也就是從左向右的過程。

  堆的特性和二分搜索樹不同,堆的某個節點總是不大於其父親節點的值。也就是它並不具有順序性。我們可以看一下下面這張圖。

二叉堆結構

可以看出任意子樹的最大值永遠是自己的父親節點。

2.2、二叉堆的結構

  這裏我們可以看出來二叉堆是一層一層的從左到右這麼依次排列的,所以這裏我們使用數組進行存儲二叉樹。通過數組索引找到節點。

  這樣我們就非常巧妙的將樹結構存儲到了數組當中。我們還可以發現下面的規則:

  • 左孩子的索引等於該父親節點索引值的 2 倍 + 1
  • 有孩子的索引等於該父親節點的索引值的 2 倍 + 2
  • 父親節點的索引值等於左右孩子節點的(索引值 - 1) / 2

用代碼展示就是:

parent(i) = (i - 1)/ 2;//獲得i索引值的父親節點索引值
leftChild(i) = i * 2 + 1
rightChild(i) = i * 2 + 2

2.3、初始化操作

  在最大堆這個數據結構當中我們使用的是數組的底層實現,當然我們也就需要動態數組來實現這個動態大小的最大堆。關於Array動態數組這一章可以參考Array 動態數組。當然也可以直接使用Java自帶的動態數組。

初始化程序實現:

public MaxHeap() {
    data = new Array<>();
}
public MaxHeap(int capacity) {
    data = new Array<>(capacity);
}

節點索引查詢實現:
我們需要對查詢父親節點進行判斷,因爲index-1操作會導致負值出現。

private int parent(int index) {
    if (index == 0)
        throw new IllegalArgumentException("index isn't zero");
    return (index - 1) / 2;
}

private int leftChild(int index) {
    return index * 2 + 1;
}

private int rightChild(int index) {
    return index * 2 + 2;
}

2.4、添加元素

  這裏的操作底層實現其實是上浮(SiftUp)操作。下面我們就來看看是如何上浮的。

  • 向數組末尾添加一個元素,也就是向樹的最下角添加一個元素;
  • 根據堆的性質,父親節點大於它的左右孩子節點,來進行替換操作
  • 不斷進行第二步操作直到待添加節點小於它的父親節點
SiftUp操作

程序實現:

public void add(E e) {
    data.addLast(e);
    siftUp(data.getSize() - 1);
}

private void siftUp(int index) {
    while (index > 0 && data.get(parent(index)).compareTo(data.get(index)) < 0) {
        data.swap(parent(index), index);
        index = (index - 1) / 2; //父親節點也就是待插入元素現在的位置
    }
}

2.5、提取最大值

  對於我們上面實現的最大堆,看得出來,最大值的地方存在於根節點的位置。也就是數組索引位0的位置。而且我們需要維護二叉堆的性質。
步驟:

  • 用樹最後一個節點移動到根節點
  • 判斷待下沉操作的節點必須大於孩子節點的最大值,如果大於那麼循環結束,否則替換孩子節點最大值和待下沉節點的位置。
SiftDown操作

提取最大值程序實現:

public E extractMax() {
    E ret = findMax(); //查找最大值
    data.swap(0, size() - 1); //移動最後一個節點到根節點
    data.removeLast(); 
    siftDown(0);
    return ret;
}
// 下沉操作
private void siftDown(int index) {
    while (leftChild(index) < size()) {
        int j = leftChild(index);
        if (j + 1 < data.getSize() && data.get(j + 1).compareTo(data.get(j)) > 0)
            j++;  //右孩子節點值大
        if (data.get(index).compareTo(data.get(j)) >= 0)
            break;
        else
            data.swap(index, j);
        index = j;
    }
}

2.6、查詢操作

  查詢操作就是查找元素最大的值,這裏就是根節點位置,也就是索引爲 0 的位置。
程序實現:

public E findMax() {
    if (isEmpty())
        throw new IllegalArgumentException("Empty");
    return data.get(0);
}

2.7、replace操作

  replace替換操作主要包括:去除最大元素,放入一個新的元素。這其實是一個組合操作。但這裏我們準備封裝一下,並對其進行優化。

  優化的方式就是在刪除元素這裏,如果我們分extraMax和add操作就需要兩次 O(log(N)) 級別的時間複雜度。在replace操作中,我們可以直接將待添加的元素元素替換到根節點的位置,然後在執行下沉操作就可以,這樣就是一次 O(log(N)) 級別的時間複雜度。

程序實現:

public E replace(E e) {
    E ret = findMax();
    data.set(0, e);
    siftDown(0);
    return ret;
}

2.8、Heapify數組堆化

  操作就是將任意數組整理成堆的形狀。
具體的過程就是:

  • 找到樹結構的倒數第一個非葉子節點;
  • 不斷向上進行下沉SiftDown操作
Heapify數組堆化

初始位置的查詢就是最後一個節點的父親節點。

程序實現:

public MaxHeap(E[] arr) {
    data = new Array<>(arr);
    for (int i = parent(arr.length - 1); i >= 0; i--)
        siftDown(i);
}

3、優先隊列的實現——基於二叉堆

  具體的函數方法其實在最大堆已經映射過了。

優先隊列 最大堆
入隊操作 enqueue add
出隊操作 dequeue extraMax
查詢棧頂元素 getFront findMax
@Override
public E dequeue() {
    return maxHeap.extractMax();
}

@Override
public void enqueue(E e) {
    maxHeap.add(e);
}

@Override
public E getFront() {
    return maxHeap.findMax();
}

最後

更多精彩內容,大家可以轉到我的主頁:曲怪曲怪的主頁

或者關注我的微信公衆號:TeaUrn

源碼地址:可在公衆號內回覆 數據結構與算法源碼 即可獲得。

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