PriorityQueue的源碼閱讀

什麼是PriorityQueue,能保證內部的有序(不是全有序,而是每次都能取到最小值)
去年第一次在算法題中使用到PriorityQueue,今天又遇到了,好奇它是如何實現的,今天來看一下源碼。看源碼的時候,開始是雲裏霧裏的,直到見到這麼一個方法heapify,數組建堆,立馬就清楚了。
先說一下heapify也就是PriorityQueue的核心,把一個數組看成一個二叉樹。
如圖:

      0
     / \
    1   2
   / \ / \
  3  4 5  6
 / \
7   8

很明顯了,下標是3的子節點爲7,8;第k位的父節點是(k-1)/2;我們只要保證一件事
規定1:子節點一定不小於父節點;
這樣就行咯。這樣根節點就是最小的。我們只要保證在刪除和插入時候,保證規定1,就永遠能保存poll的都是最小值。
現在開始看源碼:
構造器就不看了,就是建立數組,和ArrayList很像,你不指定長度,就給默認長度,如果不傳comparator,就設爲null(comparator的作用下面會講),至於Collection的構造器,我們後面講。
直接看add代碼(add進來就是隻有offer):

public boolean offer(E e) {
    if (e == null)
		//確保不插入空值(空值無法判斷)
        throw new NullPointerException();
	//modCount作用都是一樣的,fail-fast
    modCount++;
    int i = size;
    if (i >= queue.length)
		//如果數組長度不夠,擴容
        grow(i + 1);
    size = i + 1;
    if (i == 0)
		//這個簡單,就是放在0位
        queue[0] = e;
    else
		//重點這個
        siftUp(i, e);
    return true;
}

private void siftUp(int k, E x) {
	//這兩個的邏輯是一樣的,就是構造器是否傳comparator,如果有,就按照構造器的來,如果沒有,就按對象的自己的比較,那我們就看第二個方法
    if (comparator != null)
        siftUpUsingComparator(k, x);
    else
        siftUpComparable(k, x);
}

//在k位插入x(非直接插,是否還要往上走)
private void siftUpComparable(int k, E x) {
	//既然你構造器沒有comparator,那我們就任務你實現了Comparable接口,不然就報ClassCastException錯誤
    Comparable<? super E> key = (Comparable<? super E>) x;
	//k==0時是不用判斷的,已經是根節點了
    while (k > 0) {
		//看上面的樹知道,這是在找父節點
        int parent = (k - 1) >>> 1;
        Object e = queue[parent];
		//插入的值比父節點小,就不用比了
        if (key.compareTo((E) e) >= 0)
            break;
		//插入的更小,那父節點的值下移唄,繼續往上比咯
        queue[k] = e;
        k = parent;
    }
    queue[k] = key;
}

至此,add的代碼是看完了,第二重要的就是poll()了,peek就不看了,直接返回根節點的值

public E poll() {
    if (size == 0)
        return null;
    int s = --size;
    modCount++;
	//把要返回的準備好
    E result = (E) queue[0];
	//現在0位空了,得有人入住啊,那就把最後一位拿過來唄
    E x = (E) queue[s];
	//最後一位,你現在空了哈
    queue[s] = null;
    if (s != 0)
		//最後一位直接入住第0位啊,想得美,往下判斷
        siftDown(0, x);
    return result;
}

//和add一樣,不解釋了
private void siftDown(int k, E x) {
    if (comparator != null)
        siftDownUsingComparator(k, x);
    else
        siftDownComparable(k, x);
}

//在第k位插入x(插入之前先判斷,是否往下走)
private void siftDownComparable(int k, E x) {
	//不解釋
    Comparable<? super E> key = (Comparable<? super E>)x;
	//小於half的纔有子節點
    int half = size >>> 1;        // loop while a non-leaf
    while (k < half) {
		//左子節點的key
        int child = (k << 1) + 1; // assume left child is least
        Object c = queue[child];
		//右子節點的key
        int right = child + 1;
        if (right < size &&
            ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
			//說這麼多,就是選一個左右節點裏的較小的那個
            c = queue[child = right];
		//子節點不夠小時,直接結束
        if (key.compareTo((E) c) <= 0)
            break;
		//子節點的值上移,繼續往下走
        queue[k] = c;
        k = child;
    }
    queue[k] = key;
}

至此,poll的操作也結束了,核心方法直接結束

下面就是remove(Object o)方法

public boolean remove(Object o) {
	//找到o的位置,indexOf的方法很簡單,for遍歷(因爲不是真的有序)
    int i = indexOf(o);
    if (i == -1)
        return false;
    else {
		//看看它
        removeAt(i);
        return true;
    }
}

//移除第i位
private E removeAt(int i) {
    // assert i >= 0 && i < size;
    modCount++;
    int s = --size;
    if (s == i) // removed last element
		//移除的恰好是最後一位,可以結束了
        queue[i] = null;
    else {
        E moved = (E) queue[s];
        queue[s] = null;
		//這是老方法了,把最後一位的值向下比較插入到空的i位
        siftDown(i, moved);
        if (queue[i] == moved) {
			//插在第i位,可能有問題,因爲最後一位的值不一定更大,所以還要向上比較插入
            siftUp(i, moved);
            if (queue[i] != moved)
				//至於返回嘛,這是迭代器纔要的功能,就不管了
                return moved;
        }
    }
    return null;
}

remove()就簡單了,就是poll();只不過是爲空時會報錯。

還有一類的構造器沒有講,就是傳Collection的構造器

public PriorityQueue(Collection<? extends E> c) {
	//分成兩類,是不是SortedSet或PriorityQueue
	//說一下核心思想吧,就是原本是否有序,SortedSet和PriorityQueue是有序的,就不需要後面的建堆操作
    if (c instanceof SortedSet<?>) {
        SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
        this.comparator = (Comparator<? super E>) ss.comparator();
		//純粹的數組拷貝,加上判斷空值
        initElementsFromCollection(ss);
    }
    else if (c instanceof PriorityQueue<?>) {
        PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c;
        this.comparator = (Comparator<? super E>) pq.comparator();
        initFromPriorityQueue(pq);
    }
    else {
        this.comparator = null;
		//initElementsFromCollection(c);+heapify();建堆的操作,就是siftDown操作
        initFromCollection(c);
    }
}

addAll(Collection<? extends E> c)的方法就簡單了,就是循環之後的add
至此,除了迭代的PriorityQueue的主要方法已經看完,那不就是數組當堆看嘛。

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