優先佇列
不僅僅是“先進先出”,而是可以插隊。
優先順序、關鍵碼(Key)、全序關係與優先佇列
將關鍵碼定義為任意物件,必須建立一種統一的、相容的形式,以支援不同物件之間的比較,確定優先順序。 實際上,作為優先佇列的一個基本要求,在關鍵碼之間必須能夠定義某種全序關係(Total order relation)。 具體來說,任何兩個關鍵碼都必須能夠比較“大小”。
舉個例子:如果將這種全序關係用“≤”表示,則該關係還必須滿足以下三條性質:
- 自反性:對於任一關鍵碼 k,都有 k ≤ k
- 反對稱性:若k1 ≤ k2且k2 ≤ k1,則k1 = k2
- 傳遞性:若k1 ≤ k2且k2 ≤ k3,則k1 ≤ k3
所謂的優先佇列也是物件的一種容器,只不過其中的每個物件都擁有一個關鍵碼,在它們的關 鍵碼之間存在滿足上述性質的某種全序關係“≤”。關鍵碼可以是在物件插入優先佇列時被人為賦予 的,也可能就是物件本身具有的某一屬性。
-
關鍵碼:物件
-
全序關係:if(){}else(){}
關鍵碼通過全序關係確認優先順序排成優先佇列。
條目(Entry)
所謂一個條目(Entry),就是由一個物件及 其關鍵碼合成的一個物件,它反映和記錄了二者之間的關聯關係。
這樣,通過將條目物件作為優先佇列的元素,即可記錄和維護原先物件與其關鍵碼之間的關聯關係。
/**
* <b>Description:</b> 引入條目這一概念,旨在解決上面的前一個問題。
* 所謂一個條目(Entry),就是由一個物件及其關鍵碼合成的一個物件,它反映和記錄了二者之間的關聯關係。
* 這樣,通過將條目物件作為優先佇列的元素,即可記錄和維護原先物件與其關鍵碼之間的關聯關係。
* @author tongson
*/
public interface Entry {
/**
* 取條目的關鍵碼
*
* @return
*/
public Object getKey();
/**
* 修改條目的關鍵碼,返回此前存放的關鍵碼
*
* @param k
* @return
*/
public Object setKey(Object k);
/**
* 取條目的資料物件
*
* @return
*/
public Object getValue();
/**
* 修改條目的資料物件,返回此前存放的資料物件
*
* @param v
* @return
*/
public Object setValue(Object v);
}
複製程式碼
public class EntryDefault implements Entry {
protected Object key;
protected Object value;
/**************************** 建構函式 ****************************/
public EntryDefault(Object k, Object v) {
key = k;
value = v;
}
/**************************** Entry介面方法 ****************************/
/**
* 取條目的關鍵碼
*
* @return
*/
@Override
public Object getKey() {
return key;
}
/**
* 修改條目的關鍵碼,返回此前存放的關鍵碼
*
* @param k
* @return
*/
@Override
public Object setKey(Object k) {
Object oldK = key;
key = k;
return oldK;
}
/**
* 取條目的資料物件
*
* @return
*/
@Override
public Object getValue() {
return value;
}
/**
* 修改條目的資料物件,返回此前存放的資料物件
*
* @param v
* @return
*/
@Override
public Object setValue(Object v) {
Object oldV = value;
value = v;
return oldV;
}
}
複製程式碼
比較器
基於某種 Comparable 介面實現一個關鍵碼類,並將所有通常的比較方法封裝起來,以支援關鍵碼之間的比較。
/**
* 基於某種 Comparable 介面實現一個關鍵碼類,並將所有通常的比較方法封裝起來,以支援關鍵碼之間的比較。
*/
public interface Comparator {
/**
* 若a>(=或<)b,返回正數、零或負數
*
* @param a
* @param b
* @return
*/
public int compare(Object a, Object b);
}
複製程式碼
/**
* Comparable物件的預設比較器
*/
public class ComparatorDefault implements Comparator {
public ComparatorDefault() {
}
@Override
public int compare(Object a, Object b) throws ClassCastException {
return ((Comparable) a).compareTo(b);
}
}
複製程式碼
優先佇列(interface)
public interface PQueue {
/**統計優先佇列的規模
*
* @return
*/
public int getSize();
/**判斷優先佇列是否為空
*
* @return
*/
public boolean isEmpty();
/**若Q非空,則返回其中的最小條目(並不刪除);否則,報錯
*
* @return
* @throws ExceptionPQueueEmpty
*/
public Entry getMin() throws ExceptionPQueueEmpty;
/**將物件obj與關鍵碼k合成一個條目,將其插入Q中,並返回該條目
*
* @param key
* @param obj
* @return
* @throws ExceptionKeyInvalid
*/
public Entry insert(Object key, Object obj) throws ExceptionKeyInvalid;
/**若Q非空,則從其中摘除關鍵碼最小的條目,並返回該條目;否則,報錯
*
* @return
* @throws ExceptionPQueueEmpty
*/
public Entry delMin() throws ExceptionPQueueEmpty;
}
複製程式碼
排序器(interface)
/**
* <b>Description:</b> 排序器介面 <br>
*/
public interface Sorter {
void sort(Sequence S);
}
複製程式碼
基於優先佇列的排序器
/**
* <b>Description:</b> 基於優先佇列的排序器 <br>
*/
public class SorterPQueue implements Sorter {
private Comparator C;
public SorterPQueue() {
this(new ComparatorDefault());
}
public SorterPQueue(Comparator comp) {
C = comp;
}
@Override
public void sort(Sequence S) {
//為批處理建立優先佇列而準備的序列
Sequence T = new SequenceDLNode();
//構建序列T
while (!S.isEmpty()) {
//逐一取出S中各元素
Object e = S.removeFirst();
//用節點元素本身作為關鍵碼
T.insertLast(new EntryDefault(e, e));
}
// PQueue pq = new PQueueUnsortedList(C, T);
// PQueue pq = new PQueueSortedList(C, T);
PQueue pq = new PQueueHeap(C, T);
//從優先佇列中不斷地
while (!pq.isEmpty()) {
//取出最小元素,插至序列末尾
S.insertLast(pq.delMin().getValue());
System.out.println("\t:\t" + S.last().getElem());
}
}
}
複製程式碼
用向量實現優先佇列
用列表實現優先佇列
基於無序列表的實現及分析
/**
* <b>Description:</b> 基於無序列表的實現及分析 <br>
*/
public class PQueueUnsortedList implements PQueue {
private List L;
private Comparator C;
/**
* 構造方法(使用預設比較器)
*/
public PQueueUnsortedList() {
this(new ComparatorDefault(), null);
}
/**
* 構造方法(使用指定比較器)
*
* @param c
*/
public PQueueUnsortedList(Comparator c) {
this(c, null);
}
/**
* 構造方法(使用指定初始元素)
*
* @param s
*/
public PQueueUnsortedList(Sequence s) {
this(new ComparatorDefault(), s);
}
/**
* 構造方法(使用指定比較器和初始元素)
*
* @param c
* @param s
*/
public PQueueUnsortedList(Comparator c, Sequence s) {
L = new ListDLNode();
C = c;
if (null != s) {
while (!s.isEmpty()) {
Entry e = (Entry) s.removeFirst();
insert(e.getKey(), e.getValue());
}
}
}
/**
* 統計優先佇列的規模
*
* @return
*/
@Override
public int getSize() {
return L.getSize();
}
/**
* 判斷優先佇列是否為空
*
* @return
*/
@Override
public boolean isEmpty() {
return L.isEmpty();
}
/**
* 若Q非空,則返回其中的最小條目(並不刪除);否則,報錯
*
* @return
* @throws ExceptionPQueueEmpty
*/
@Override
public Entry getMin() throws ExceptionPQueueEmpty {
if (L.isEmpty()) {
throw new ExceptionPQueueEmpty("意外:優先佇列空");
}
Position minPos = L.first();
Position curPos = L.getNext(minPos);
//依次檢查所有位置,找出最小條目
while (null != curPos) {
if (0 < C.compare(minPos.getElem(), curPos.getElem())) {
minPos = curPos;
}
}
return (Entry) minPos.getElem();
}
/**
* 將物件obj與關鍵碼k合成一個條目,將其插入Q中,並返回該條目
*
* @param key
* @param obj
* @return
* @throws ExceptionKeyInvalid
*/
@Override
public Entry insert(Object key, Object obj) throws ExceptionKeyInvalid {
//建立一個新條目
Entry entry = new EntryDefault(key, obj);
//接至列表末尾
L.insertLast(entry);
return (entry);
}
/**
* 若Q非空,則從其中摘除關鍵碼最小的條目,並返回該條目;否則,報錯
*
* @return
* @throws ExceptionPQueueEmpty
*/
@Override
public Entry delMin() throws ExceptionPQueueEmpty {
if (L.isEmpty()) {
throw new ExceptionPQueueEmpty("意外:優先佇列空");
}
Position minPos = L.first();
Iterator it = L.positions();
//依次檢查所有位置,找出最小條目
while (it.hasNext()) {
Position curPos = (Position) (it.getNext());
// System.out.println("\t" + ((Entry)(curPos.getElem())).getKey());
if (0 < C.compare(((Entry) (minPos.getElem())).getKey(), ((Entry) (curPos.getElem())).getKey())) {
minPos = curPos;
}
}
return (Entry) L.remove(minPos);
}
}
複製程式碼
基於有序列表的實現及分析
/**
* <b>Description:</b> 基於有序列表的實現及分析 <br>
*/
public class PQueueSortedList implements PQueue {
private List L;
private Comparator C;
/**
* 構造方法(使用預設比較器)
*/
public PQueueSortedList() {
this(new ComparatorDefault(), null);
}
/**
* 構造方法(使用指定比較器)
*
* @param c
*/
public PQueueSortedList(Comparator c) {
this(c, null);
}
/**
* 構造方法(使用指定初始元素)
*
* @param s
*/
public PQueueSortedList(Sequence s) {
this(new ComparatorDefault(), s);
}
/**
* 構造方法(使用指定比較器和初始元素)
*
* @param c
* @param s
*/
public PQueueSortedList(Comparator c, Sequence s) {
L = new ListDLNode();
C = c;
if (null != s)
while (!s.isEmpty()) {
Entry e = (Entry) s.removeFirst();
insert(e.getKey(), e.getValue());
}
}
/**
* 統計優先佇列的規模
*
* @return
*/
@Override
public int getSize() {
return L.getSize();
}
/**
* 判斷優先佇列是否為空
*
* @return
*/
@Override
public boolean isEmpty() {
return L.isEmpty();
}
/**
* 若Q非空,則返回其中的最小條目(並不刪除);否則,報錯
*
* @return
* @throws ExceptionPQueueEmpty
*/
@Override
public Entry getMin() throws ExceptionPQueueEmpty {
if (L.isEmpty()) {
throw new ExceptionPQueueEmpty("意外:優先佇列空");
}
return (Entry) L.last();
}
/**
* 將物件obj與關鍵碼k合成一個條目,將其插入Q中,並返回該條目
*
* @param key
* @param obj
* @return
* @throws ExceptionKeyInvalid
*/
@Override
public Entry insert(Object key, Object obj) throws ExceptionKeyInvalid {
//建立一個新條目
Entry entry = new EntryDefault(key, obj);
//若優先佇列為空 //或新條目是當前最大者
if (L.isEmpty() || (0 > C.compare(((Entry) (L.first().getElem())).getKey(), entry.getKey()))) {
//則直接插入至表頭
L.insertFirst(entry);
} else {//否則
//從尾條目開始
Position curPos = L.last();
while (0 > C.compare(((Entry) (curPos.getElem())).getKey(), entry.getKey())) {
//不斷前移,直到第一個不小於entry的條目
curPos = L.getPrev(curPos);
}
//緊接該條目之後插入entry
L.insertAfter(curPos, entry);
}
return (entry);
}
/**
* 若Q非空,則從其中摘除關鍵碼最小的條目,並返回該條目;否則,報錯
*
* @return
* @throws ExceptionPQueueEmpty
*/
@Override
public Entry delMin() throws ExceptionPQueueEmpty {
if (L.isEmpty()) {
throw new ExceptionPQueueEmpty("意外:優先佇列空");
}
return (Entry) L.remove(L.last());
}
}
複製程式碼
選擇排序與插入排序
選擇排序
插入排序
效率比較
用堆實現優先佇列
堆的定義及性質
堆結構
完全性
用堆實現優先佇列
/**
* <b>Description:</b> 基於堆的優先佇列及其實現 <br>
*/
public class PQueueHeap implements PQueue {
/**
* 完全二叉樹形式的堆
*/
private ComplBinTree H;
/**
* 比較器
*/
private Comparator comp;
/**
* 構造方法
*/
public PQueueHeap() {
this(new ComparatorDefault(), null);
}
/**
* 構造方法:預設的空優先佇列
*
* @param c
*/
public PQueueHeap(Comparator c) {
this(c, null);
}
/**
* 構造方法:根據某一序列直接批量式構造堆演算法,S中元素都是形如(key, value)的條目
*
* @param S
*/
public PQueueHeap(Sequence S) {
this(new ComparatorDefault(), S);
}
/**
* 構造方法:根據某一序列直接批量式構造堆演算法,s中元素都是形如(key, value)的條目
*
* @param c
* @param s
*/
public PQueueHeap(Comparator c, Sequence s) {
comp = c;
H = new ComplBinTreeVector(s);
if (!H.isEmpty()) {
//自底而上
for (int i = H.getSize() / 2 - 1; i >= 0; i--) {
//逐節點進行下濾
percolateDown(H.posOfNode(i));
}
}
}
/*-------- PQueue介面中定義的方法 --------*/
/**
* 統計優先佇列的規模
*
* @return
*/
@Override
public int getSize() {
return H.getSize();
}
/**
* 判斷優先佇列是否為空
*
* @return
*/
@Override
public boolean isEmpty() {
return H.isEmpty();
}
/**
* 若Q非空,則返回其中的最小條目(並不刪除);否則,報錯
*
* @return
* @throws ExceptionPQueueEmpty
*/
@Override
public Entry getMin() throws ExceptionPQueueEmpty {
if (isEmpty()) {
throw new ExceptionPQueueEmpty("意外:優先佇列為空");
}
return (Entry) H.getRoot().getElem();
}
/**
* 將物件obj與關鍵碼k合成一個條目,將其插入Q中,並返回該條目
*
* @param key
* @param obj
* @return
* @throws ExceptionKeyInvalid
*/
@Override
public Entry insert(Object key, Object obj) throws ExceptionKeyInvalid {
checkKey(key);
Entry entry = new EntryDefault(key, obj);
percolateUp(H.addLast(entry));
return entry;
}
/**
* 若Q非空,則從其中摘除關鍵碼最小的條目,並返回該條目;否則,報錯
*
* @return
* @throws ExceptionPQueueEmpty
*/
@Override
public Entry delMin() throws ExceptionPQueueEmpty {
if (isEmpty()) {
throw new ExceptionPQueueEmpty("意外:優先佇列為空");
}
//保留堆頂
Entry min = (Entry) H.getRoot().getElem();
//若只剩下最後一個條目
if (1 == getSize()) {
//直接摘除之
H.delLast();
} else {//否則
H.getRoot().setElem(((ComplBinTreeNodeRank) H.delLast()).getElem());
//取出最後一個條目,植入堆頂
percolateDown(H.getRoot());
}
//返回原堆頂
return min;
}
/*-------- 輔助方法 --------*/
/**
* 檢查關鍵碼的可比較性
*
* @param key
* @throws ExceptionKeyInvalid
*/
protected void checkKey(Object key) throws ExceptionKeyInvalid {
try {
comp.compare(key, key);
} catch (Exception e) {
throw new ExceptionKeyInvalid("無法比較關鍵碼");
}
}
/**
* 返回節點v(中所存條目)的關鍵碼
*
* @param v
* @return
*/
protected Object key(BinTreePosition v) {
return ((Entry) (v.getElem())).getKey();
}
/*-------- 演算法方法 --------*/
/**
* 交換父子節點(中所存放的內容)
*
* @param u
* @param v
*/
protected void swapParentChild(BinTreePosition u, BinTreePosition v) {
Object temp = u.getElem();
u.setElem(v.getElem());
v.setElem(temp);
}
/**
* 上濾演算法
*
* @param v
*/
protected void percolateUp(BinTreePosition v) {
//記錄根節點
BinTreePosition root = H.getRoot();
//不斷地
while (v != H.getRoot()) {
//取當前節點的父親
BinTreePosition p = v.getParent();
if (0 >= comp.compare(key(p), key(v))) {
//除非父親比孩子小
break;
}
//否則,交換父子次序
swapParentChild(p, v);
//繼續考察新的父節點(即原先的孩子)
v = p;
}
}
/**
* 下濾演算法
*
* @param v
*/
protected void percolateDown(BinTreePosition v) {
//直到v成為葉子
while (v.hasLChild()) {
//首先假設左孩子的(關鍵碼)更小
BinTreePosition smallerChild = v.getLChild();
if (v.hasRChild() && 0 < comp.compare(key(v.getLChild()), key(v.getRChild()))) {
//若右孩子存在且更小,則將右孩子作為進一步比較的物件
smallerChild = v.getRChild();
}
if (0 <= comp.compare(key(smallerChild), key(v))) {
//若兩個孩子都不比v更小,則下濾完成
break;
}
//否則,將其與更小的孩子交換
swapParentChild(v, smallerChild);
//並繼續考察這個孩子
v = smallerChild;
}
}
}
複製程式碼
基於堆的優先佇列及其實現
堆排序
直接堆排序
就地堆排序
Huffman 樹
氣泡排序
/**
* <b>Description:</b> 起泡排序演算法 <br>
*/
public class SorterBubblesort implements Sorter {
private Comparator C;
public SorterBubblesort() {
this(new ComparatorDefault());
}
public SorterBubblesort(Comparator comp) {
C = comp;
}
@Override
public void sort(Sequence S) {
int n = S.getSize();
for (int i = 0; i < n; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (0 < C.compare(S.getAtRank(j), S.getAtRank(j + 1))) {
Object temp = S.getAtRank(j);
S.replaceAtRank(j, S.getAtRank(j + 1));
S.replaceAtRank(j + 1, temp);
}
}
}
}
}
複製程式碼