數據結構:優先隊列
引入優先隊列
說明
優先隊列是一種抽象數據類型,它是一種排序的機制,它有兩個核心操作:找出鍵值最大(優先級最高)的元素、插入新的元素,效果就是他在維護一個動態的隊列。可以收集一些元素,並快速取出鍵值最大的元素,對其操作后移出隊列,然后再收集更多的元素,再處理當前鍵值最大的元素,如此這般。
例如,我們有一台能夠運行多個程序的計算機。計算機通過給每個應用一個優先級屬性,將應用根據優先級進行排列,計算機總是處理下一個優先級最高的元素。
泛型優先隊列的API
優先隊列最重要的操作是刪除最大元素和插入元素。

優先隊列的初級實現
數組實現(無序)
►思想:
我們維護一個數組,因為不考慮數組順序,所以我們的插入算法就很簡單了。
對於查找最大值,我們利用了選擇排序,在找到最大值后,將其與最后一個元素交換,並使長度-1.
► 只給出最簡單核心實現步驟:
package queueDemo;
public class QueueANO<T extends Comparable<T>> {
private T[] array;
private int n;
public QueueANO(int capacity)
{
array=(T[]) new Comparable[capacity];
n=0;
}
........
public void insert(T t)
{
array[n]=t;n++;
}
public T delMax()
{
int max=0;
for(int i=1;i<n;i++) //找出最大元素
{
if(less(max,i))
max=i;
}
exch(max,n-1); //將最大元素交換到最后
n--; //長度-1
return array[n];
}
}
數組實現(有序)
►思想:
由於我們維護一個有序數組,所以每次插入元素的時候都要給他找到一個合適位置,來保證數組有序性,刪除操作就會很簡單了。
►代碼:
public class OrderArrayPriorityQueue <Key extends Comparable<Key>>{
private Key[] pq; // elements
private int n; // number of elements
public OrderArrayPriorityQueue(int capacity) {
pq = (Key[]) (new Comparable[capacity]);
n = 0;
}
public boolean isEmpty() { return n == 0; }
public int size() { return n; }
public Key delMax() { return pq[--n]; }
public void insert(Key key) {
int i = n-1;
while (i >= 0 && less(key, pq[i])) {
pq[i+1] = pq[i];
i--;
}
pq[i+1] = key;
n++;
}
private boolean less(Key v, Key w) {
return v.compareTo(w) < 0;
}
public static void main(String[] args) {
OrderArrayPriorityQueue<String> pq = new OrderArrayPriorityQueue<String>(10);
pq.insert("this");
pq.insert("is");
pq.insert("a");
pq.insert("test");
while (!pq.isEmpty())
System.out.println(pq.delMax());
}
}
堆的定義
說明
二叉堆能夠很好的實現優先隊列的基本操作,二叉堆就是一顆二叉樹,但是是按一種特定的組織結構排列。即在二叉堆中每一個節點的值都要保證大於等於另外子節點的值,這也稱為大頂堆,即頭重腳輕。還有一種排列方式是自上而下依次升高,即每一個節點的值都小於等於其子節點的值,稱之為小頂堆。
圖示
如下圖所示的是一個大頂堆,其根節點一定是所有元素中最大的一個,即優先性最高的,當我們取走后,取代其位置的也應是下一個最大的元素。

說明:
這是一個堆有序的二叉樹。所謂堆有序就是一顆二叉樹的每個節點都大於等於(或小於)它的兩個子節點。
二叉堆表示法
我們可以使用指針來表示,但是這並不是最方便的。通過觀察二叉有序堆,我們會發現它是一種完全二叉樹,並且完全二叉樹可以用數組來表示。用數組實現二叉有序堆,具體方法就是將二叉樹的節點按照層序順序放入數組中,根節點位置在1,它的子節點位置在2,3.依次類推。
兩條重要的性質:
1.在一個二叉堆中,位置為K的節點的父節點的位置為|_K/2_|,而它的兩個子節點位置為2K和2K+1
2.一顆大小為N的完全二叉樹的高度為|_LgN_|
圖示堆排序
堆排序實質是對一組關鍵字進行建堆的過程,這一過程可稱為堆的有序化。我們此處將的是大頂堆,小頂堆的道理是相同的。
插入新的元素進行有序化
如下圖所示,我們的目標是大頂堆,然而新插入的元素值為9,大於其父元素,所以我們需要進行有序化:

我們將子元素設為X(圖中值為9),我們需要交換它和它的父節點(值為6)來修復堆。但是可能交換后X還是很大(大於值為8.5的元素),所以我們需要X一次次的它的祖先節點進行比較,直到找打它最合適的位置。根據二叉堆的性質,我們不難發現只要記住位置為K的節點的父節點為 |_K/2_|,一切都很簡單了。

這就是一種上浮操作,即新插入的元素進行上浮,就要需要一次次的它的祖先節點進行比較,直到找打它最合適的位置。
上浮操作核心代碼如下:
private void swim(int k) {
while (k > 1 && less(k/2,k)) {
exch(k/2, k);
k = k/2;
}
}
刪除堆頂元素后進行有序化
在堆排序中,我們是如何處理刪除堆頂元素的呢?我們首先將堆頂元素與序列末端元素進行交換,然后刪除末端元素。這是堆頂元素肯定不是堆中最大的元素,所以他需要找到他合適的位置。

為值為6的元素找到其合適位置,它需要和它的子節點中較大的節點進行交換來修復堆,但是可能交換后X還是很小,所以我們需要X一次次的它的子節點進行比較並交換,直到找打它最合適的位置。

這是一種下沉操作,即被交換后的元素,需要一次次的它的子節點進行比較並交換,直到找打它最合適的位置。
下沉操作核心代碼如下:
private void sink(int k) {
while (2 * k <= N) {
int j = 2 * k;
if (j < N && less(j, j + 1)) {
j++;
}
if (!less(k, j)) {
break;
}
exch(k, j);
k = j;
}
}
到這里位置,我們已經學會了在堆中插入一個新元素和刪除堆頂元素的操作,這已然是堆排序的核心內容了。
Java版本實現代碼
class MaxPQ<Key extends Comparable<Key>> {
private Key[] pq;
private int N = 0;
public MaxPQ(int maxN) {
pq = (Key[]) new Comparable[maxN + 1];
}
public static void main(String[] args) {
MaxPQ<Integer> maxPQ = new MaxPQ<Integer>(10);
for(int i = 0; i < 10; i++)
{
maxPQ.insert((int)(Math.random() * 10 + 1));
}
while(!maxPQ.isEmpty())
{
System.out.println(maxPQ.delMax());
}
}
public int size() {
return N;
}
public boolean isEmpty() {
return N == 0;
}
public void insert(Key v) {
pq[++N] = v;
swim(N);
}
public Key delMax() {
Key max = pq[1];
exch(1,N--);
pq[N + 1] = null;
sink(1);
return max;
}
private boolean less(int i, int j) {
return pq[i].compareTo(pq[j]) < 0;
}
private void exch(int i, int j) {
Key temp = pq[i];
pq[i] = pq[j];
pq[j] = temp;
}
private void sink(int k) {
while (2 * k <= N) {
int j = 2 * k;
if (j < N && less(j, j + 1)) {
j++;
}
if (!less(k, j)) {
break;
}
exch(k, j);
k = j;
}
}
private void swim(int k) {
while (k > 1 && less(k/2,k)) {
exch(k/2, k);
k = k/2;
}
}
}
