在堆排序這篇文章中千辛萬苦的實現了堆的結構和排序,其實在Java 1.5版本後就提供了一個具備了小根堆性質的數據結構也就是優先隊列PriorityQueue。下面詳細瞭解一下PriorityQueue到底是如何實現小頂堆的,然後利用PriorityQueue實現大頂堆。
PriorityQueue的數據結構
PriorityQueue的邏輯結構是一棵完全二叉樹,存儲結構其實是一個數組。邏輯結構層次遍歷的結果剛好是一個數組。
PriorityQueue的操作
①add(E e) 和 offer(E e) 方法
add(E e) 和 offer(E e) 方法都是向PriorityQueue中加入一個元素,其中add()其實調用了offer()方法如下:
public boolean add(E e) {
return offer(e);
}
- 1
- 2
- 3
下面主要看看offer()方法的作用:
如上圖調用 offer(4)方法後,往堆中壓入4然後從下往上調整堆爲小頂堆。offer()的代碼實現:
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
//如果壓入的元素爲null 拋出異常
int i = size;
if (i >= queue.length)
grow(i + 1);
//如果數組的大小不夠擴充
size = i + 1;
if (i == 0)
queue[0] = e;
//如果只有一個元素之間放在堆頂
else
siftUp(i, e);
//否則調用siftUp函數從下往上調整堆。
return true;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
對上面代碼做幾點說明:
①優先隊列中不能存放空元素。
②壓入元素後如果數組的大小不夠會進行擴充,上面的queue其實就是一個默認初始值爲11的數組(也可以賦初始值)。
③offer元素的主要調整邏輯在 siftUp ( i, e )函數中。下面看看 siftUp(i, e) 函數到底是怎樣實現的。
private void siftUpComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
上面的代碼還是比較簡明的,就是當前元素與父節點不斷比較如果比父節點小就交換然後繼續向上比較,否則停止比較的過程。
② poll() 和 remove() 方法
poll 方法每次從 PriorityQueue 的頭部刪除一個節點,也就是從小頂堆的堆頂刪除一個節點,而remove()不僅可以刪除頭節點而且還可以用 remove(Object o) 來刪除堆中的與給定對象相同的最先出現的對象。先看看poll()方法。下面是poll()之後堆的操作
刪除元素後要對堆進行調整:
堆中每次刪除只能刪除頭節點。也就是數組中的第一個節點。
將最後一個節點替代頭節點然後進行調整。
如果左右節點中的最小節點比當前節點小就與左右節點的最小節點交換。直到當前節點無子節點,或者當前節點比左右節點小時停止交換。
poll()方法的源碼
public E poll() {
if (size == 0)
return null;
//如果堆大小爲0則返回null
int s = --size;
modCount++;
E result = (E) queue[0];
E x = (E) queue[s];
queue[s] = null;
//如果堆中只有一個元素直接刪除
if (s != 0)
siftDown(0, x);
//否則刪除元素後對堆進行調整
return result;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
看看 siftDown(0, x) 方法的源碼:
private void siftDownComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>)x;
int half = size >>> 1; // loop while a non-leaf
while (k < half) {
int child = (k << 1) + 1; // assume left child is least
Object c = queue[child];
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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
siftDown()方法就是從堆的第一個元素往下比較,如果比左右孩子節點的最小值小則與最小值交換,交換後繼續向下比較,否則停止比較。
remove(4)的過程圖:
先用堆的最後一個元素 5 代替4然後從5開始向下調整堆。這個過程和poll()函數一樣,只不過poll()函數每次都是從堆頂開始。
remove(Object o)的代碼:
public boolean remove(Object o) {
int i = indexOf(o);
//先在堆中找到o的位置
if (i == -1)
return false;
//如果不存在則返回false。
else {
removeAt(i);
//否則刪除數組中第i個位置的值,調整堆。
return true;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
removeAt(int 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;
siftDown(i, moved);
if (queue[i] == moved) {
siftUp(i, moved);
if (queue[i] != moved)
return moved;
}
}
return null;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
使用PriorityQueue實現大頂堆
PriorityQueue默認是一個小頂堆,然而可以通過傳入自定義的Comparator函數來實現大頂堆。如下代碼:
private static final int DEFAULT_INITIAL_CAPACITY = 11;
PriorityQueue<Integer> maxHeap=new PriorityQueue<Integer>(DEFAULT_INITIAL_CAPACITY, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
實現了一個初始大小爲11的大頂堆。這裏只是簡單的傳入一個自定義的Comparator函數,就可以實現大頂堆了。