歡迎探討,如有錯誤敬請指正
如需轉載,請注明出處 http://www.cnblogs.com/nullzx/
1. 優先隊列與索引優先隊列
優先隊列的原理大家應該比較熟悉,本質上就是利用完全二叉樹的結構實現以log2n的時間復雜度刪除隊列中的最小對象(這里以小堆頂為例)。完全二叉樹又可以通過數組下標實現索引,當插入一個對象的時候,利用上浮操作更新最小對象。當刪除堆頂最小對象時,將末尾的對象放置到堆頂上,然后執行下沉操作。
優先隊列有一個缺點,就是不能直接訪問已存在於優先隊列中的對象,並更新它們。這個問題在Dijistra算法中就有明顯的體現,有時候我們需要更新已在隊列中的頂點的距離。為此就需要設計一種新型的數據結構來解決這個問題,這就是本文要介紹的索引優先隊列。
索引優先隊用一個整數和對象進行關聯,當我們需要跟新該對象的值時,可以通這個整數進行快速索引,然后對對象的值進行更新。當然更新后的對象在優先隊列中的位置可能發生變化,這樣以保證整個隊列還是一個優先隊列。
簡易版的索引優先隊列API
| IndexPriorityQueue<T> |
|
| IndexPriorityQueue(int capacity, Comparator<T> cmp) |
構造函數,capacity表示隊列容量,cmp表示對象的比較器 |
| void enqueue(int k, T t) |
將整數k和對象t進行關聯,如果已有和k關聯的對象,則將其更新為t |
| int dequeue() |
出列,即刪除最對象素並返回與它相關的整數。 |
| void change(int k, T t) |
將和整數k和關聯的對象更新為t |
注意與對象關聯的整數k不能超過隊列的容量。
2. 索引優先隊列的實現原理
為了實現快速索引,我們首先嘗試一個簡單版本。我們創建兩個數組分別是pq,elements。elements的作用是存儲對象的引用,我們將每個對象存儲在與之相關的整數作為下標的位置中,elements存儲的對象不一定在數組中連續存放。pq存儲是與對象相關的整數值,注意數組pq是連續存放的。此時pq作為優先隊列,但是在上浮和下沉操作中,我們比較的是pq中值作為下標的elements數組中的值。這樣我們就可以實現快速索引。
下圖中,我們以字符串作為存儲的對象類型,建立一個索引優先隊列
從中我們可以看出,我們設計數組pq數組的目的。我們只需要對pq中的數值進行維護就可以實現一個優先隊列,而elements中的對象的位置保持不變(出列時會置為null),這樣就可以方便我們快速索引。比如通過elements數組我們可以知道與整數3相關的字符串為“f”。
在圖中,我們插入一個與整數10相關的字符串“b”后,pq和elements中的值如下圖所示。
假設在上圖的基礎上,我們要將與整數3相關的字符串修改為“a”,那么我們只需要讓elements[3] = “a”即可。然后去維護pq中的值。但是在維護pq中的值時出現了一個問題,我們不知道pq中哪個位置中的值為3,只能從都到尾遍歷,找到這個元素所在的位置后進行上浮和下沉操作(因為我們必須通過下標才能快速找到父節點或者孩子節點)。為了能夠快速找到pq中元素值對應的下標,我們需要額外設置一個數組qp,它的作用是存儲與對象相關的整數在pq數組中的下標,並在上浮和下沉的過程中同時維護它。
在上述的基礎上,假設我們需要將與整數3相關的字符串修改為“a”,那么我們只需要讓elements[3] = “a”,然后通過qp[3]中的值2就可以知道數組pq中值為3的下標為2,然后對pq[2]進行上浮或下沉操作。這里顯然需要進行上浮操作,那么我們要交換pq[1]和pq[2]的值。這個時候我們需要注意的是,在交換pq數組中的兩個元素的值時,我們也需要交換qp對應兩個元素的值,因為與對象相關的整數在pq的不同位置上,那么顯然該整數在pq所在的下標也變了,所以qp中的值也應該發生變化。而需要交換的qp中的兩元素的下標正好就是pq中兩元素的值。結果如下圖所示。所以我們也需要交換qp[3]和qp[10]的值。
3. 索引優先隊列的代碼實現
上述的索引優先隊列的原理中不能將數字0與對象進行關聯,因為三個數組沒有使用下標為0的位置。如果要實現與數字0進行關聯,入列時只需要每個關聯的數字加1;當出列時,我們只需要將返回的數字減1。
package datastruct;
import java.util.Arrays;
import java.util.Comparator;
public class IndexPriorityQueue<T> {
private int[] pq;
private int[] qp;
private Object[] element;
private final int capacity;
private int size;
private Comparator<? super T> cmp;
private static class Cmp<T> implements Comparator<T>{
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public int compare(T t1, T t2) {
return ((Comparable)(t1)).compareTo(t2);
}
}
private static void swap(int[] a, int i, int j){
int tmp;
tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
//與對象關聯的整數范圍是[0,capacity-1]
public IndexPriorityQueue(int capacity, Comparator<T> cmp){
this.capacity = capacity;
pq = new int[capacity+1];
qp = new int[capacity+1];
Arrays.fill(qp, -1);
element = new Object[capacity+1];
if(cmp == null){
this.cmp = new Cmp<T>();
}
}
public void enqueue(int k, T t){
k++;//使得關聯的整數可以為0
if(k > capacity){
throw new IllegalArgumentException();
}
if(qp[k] != -1){
element[k] = t;
swim(qp[k]);
sink(qp[k]);
return;
}
size++;
pq[size] = k;
qp[k] = size;
element[k] = t;
swim(size);
}
@SuppressWarnings("unchecked")
private void swim(int child){
int parent = child/2;
while(parent > 0){
if(cmp.compare((T)element[pq[child]], (T)element[pq[parent]]) < 0){
swap(pq, child, parent);
swap(qp, pq[child], pq[parent]);
child = parent;
parent = child/2;
}else{
break;
}
}
}
public int dequeue(){
if(size == 0){
throw new IllegalArgumentException();
}
int r = pq[1];
element[r] = null;
swap(pq, size, 1);
swap(qp, pq[size], pq[1]);
pq[size] = -1;
size--;
sink(1);
r--;//使得關聯的整數可以為0
return r;
}
@SuppressWarnings("unchecked")
private void sink(int parent){
int child = parent*2;
while(child <= size){
if(child + 1 <= size){
int r = cmp.compare((T)element[pq[child]], (T)element[pq[child+1]]);
child = r > 0 ? child+1 : child;
}
if(cmp.compare((T)element[pq[child]], (T)element[pq[parent]]) < 0){
swap(pq, parent, child);
swap(qp, pq[parent], pq[child]);
parent = child;
child = parent*2;
}else{
break;
}
}
}
public void change(int k, T t){
k++;
if(qp[k] == -1){
throw new IllegalArgumentException();
}
element[k] = t;
swim(qp[k]);
sink(qp[k]);
}
public int size(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
public static void main(String[] args){
IndexPriorityQueue<String> ipq = new IndexPriorityQueue<String>(11, null);
ipq.enqueue(0, "k");
ipq.enqueue(6, "d");
ipq.enqueue(3, "f");
ipq.enqueue(4, "c");
ipq.enqueue(0, "a");
while(!ipq.isEmpty()){
System.out.println(ipq.dequeue());
}
}
}
4. 參考內容
[1]. 算法(第4版)Robert Sedgewick 人民郵電出版社


