什麼是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的主要方法已經看完,那不就是數組當堆看嘛。