一個支持刪除最大元素和插入元素操作的數據結構,叫優先隊列。
我們可以用無序數組(插入O(1), 刪除最大O(n)),有序數組(插入O(n), 刪除最大O(1)),堆(插入O(logn), 刪除最大O(logn))
這里的堆指的是二叉堆。
二叉堆的定義:二叉堆是一組能夠用堆有序的完全二叉樹排序的元素,并在數組中按照層級儲存(不使用數組的第一個元素)。
堆有序:一課二叉樹的每個節點都大于等于他的兩個子節點
完全二叉樹:除了最后一層外的其他每一層都 都被完全填充,最后一層向左對齊。
一個二叉堆數組
下面來看基于堆的優先隊列的實現:
public class MaxPQ<Key extends Comparable<Key>> {
private int n;
private Key[] keys;
public MaxPQ(int capacity) {
// TODO Auto-generated constructor stub
keys = (Key[]) new Comparable[capacity+1];
n = 0;
}
private boolean less(int i, int j) {
return keys[i].compareTo(keys[j]) < 0;
}
private void exch(int i, int j) {
Key temp = keys[i];
keys[i] = keys[j];
keys[j] = temp;
}
private void swim(int k) {
while (k > 1 && less(k/2, k)) {
exch(k/2, k);
k /= 2;
}
}
private void sink(int k) {
while (k*2 <= n) {
int j = k*2;
//注意這里j < n,刪除最大值操作時n+1位為被剔除的最大值
if (j < n && less(j, j+1)) j++;
if (less(k, j)) break;
exch(k, j);
k = j;
}
}
private void resize(int newCapacity) {
Key[] copy = (Key[]) new Comparable[newCapacity];
for(int i = 1; i <= n; i++) {
copy[i] = keys[i];
}
keys = copy;
}
public void insert(Key key) {
if (n == keys.length-1) resize(2*keys.length);
keys[++n] = key;
swim(n);
}
public Key delMax() {
Key max = keys[1];
exch(1, n--);
sink(1);
keys[n+1] = null;
if ((n > 0) && (n == (keys.length-1)/4)) resize(keys.length/2);
return max;
}
}
我們抽象出了這樣一種數據結構的目的就是讓插入,刪除最大操作都為O(logn。當插入一個元素的時候,把他放數組末尾k,然后不斷與他的上一層就是k/2比較交換,直到滿足定義。當要刪除最大數時,先把數組第一位與最后以為交換,然后讓這個新頭元素不斷與下層比較交換。
下面來介紹索引優先隊列:就是給優先隊列里的每個元素一個索引,可以理解為通過此來拓展優先隊列的功能,支持更多操作
/**
* 索引優先隊列,在原先基于堆的優先序列上對每個元素增加了索引,可以看成是功能的拓展
* 比如通過索引改變某個值,或是刪除某個值,這些操作復雜度都是log(n)
* @author Administrator
*
* @param <Key>
*/
public class IndexMaxPQ<Key extends Comparable<Key>> {
private Key[] keys;
private int[] pq; //存儲索引的數組
private int[] qp; //qp[pq[i]] = i;
private int n;
public IndexMaxPQ(int maxN) {
// TODO Auto-generated constructor stub
keys = (Key[]) new Comparable[maxN + 1];
pq= new int[maxN + 1];
qp = new int[maxN+1];
for (int i = 0; i <=maxN; i++) {
qp[i] = -1;
}
}
/**
* 交換pq[i]與pq[j]
* @param i
* @param j
*/
private void exch(int i, int j) {
int temp = pq[i];
pq[i] = pq[j];
pq[j] = temp;
qp[pq[i]] = i;
qp[pq[j]] = j;
}
private boolean less(int i, int j) {
return keys[pq[i]].compareTo(keys[pq[j]]) < 0;
}
private void swim(int k) {
while (k/2 >= 1 && less(k/2, k)) {
exch(k, k/2);
k /= 2;
}
}
private void sink(int k) {
while (k*2 <= n) {
int j = k*2;
if (j < n && less(j, j+1)) j++; // j<n這步判斷很重要,在刪除最大數的時候不再將被替換到末尾的索引考慮在內
if (less(j, k)) break;
exch(j, k);
k = j;
}
}
public boolean isEmpty() {
return n==0;
}
public int size() {
return n;
}
/**
* 是否包含該索引
* @param i
* @return
*/
public boolean contains(int i) {
return qp[i] != -1;
}
/**
* 插入
* @param i 索引值
* @param key 插入值
*/
public void insert(int i, Key key) {
if (contains(i))
throw new IllegalArgumentException("index is already in the priority queue");
n++;
pq[n] = i;
qp[i] = n;
keys[i] = key;
swim(n);
}
public int maxIndex() {
if (n == 0) throw new NoSuchElementException("priority queue underFlow");
return pq[1];
}
public Key maxKey() {
if (n == 0) throw new NoSuchElementException("priority queue underFlow");
return keys[pq[1]];
}
/**
* 刪除最大值
* @return 返回被刪除的最大值的索引
*/
public int delMax() {
if (n == 0) throw new NoSuchElementException("priority queue underFlow");
int maxIndex = pq[1];
exch(1, n--);
sink(1);
assert pq[n+1] == maxIndex ;
keys[maxIndex ] = null;
pq[n+1] = -1;
qp[maxIndex ] = -1;
return maxIndex ;
}
/**
* 通過索引來更改key
* @param i 索引
* @param key 要被更改的值
*/
public void changeKey(int i, Key key) {
if (!contains(i))
throw new NoSuchElementException("index is not in the priority queue");
keys[i] = key;
swim(qp[i]);
sink(qp[i]);
}
/**
* 刪除索引為i的key
* @param i
*/
public void delete(int i) {
if (!contains(i))
throw new NoSuchElementException("index is not in the priority queue");
int index = qp[i];
exch(index, n--);
swim(index);
sink(index);
keys[i] = null;
qp[i] = -1;
}
}
實現思想是,keys[]數組在i指針處存放key,不對它做任何排序操作;pq[]數組從1開始在++n處存放指針i,然后進行堆排序形成二叉堆,這樣透過指針的key就堆有序了;qp[]數組qp[pq[k]] = k,以指針為下標存放該指針在pq[]數組中的下標。