數據結構(十二) -- 優(yōu)先隊列

一,優(yōu)先隊列

在決定病人接受治療的次序時胧砰,除了他們到達醫(yī)院的先后次序煮岁,更主要的將取決于病情的嚴重程度屉符。由這類問題可以抽象出本章將要討論的優(yōu)先隊列(Priority queue)結構。這一結構在很多應用領域都可以派上大用場良哲,比如各種事件隊列的模擬盛卡、操作系統(tǒng)中多任務的調度及中斷機制、采用詞頻調整策略的輸入法等筑凫。另外滑沧,優(yōu)先隊列也是很多高級算法的基礎,比如Huffman編碼巍实、堆排序算法都要利用優(yōu)先隊列滓技,而在采用空間掃描策略的算法中,優(yōu)先隊列是組織事件隊列的最佳形式蔫浆。

優(yōu)先隊列之所以具有廣泛的用途殖属,是得益于其高效率以及實現(xiàn)的簡捷性。正如我們將要看到的瓦盛,如果基于堆來實現(xiàn)優(yōu)先隊列洗显,則其初始化構造可以在線性時間內完成外潜,而每次插入或刪除操作都可以在O(logn)的時間內完成。


二挠唆,關鍵碼

正如醫(yī)院根據病情嚴重程度確定病人接受治療的次序一樣处窥,優(yōu)先隊列中各對象之間的次序是由它們共同的某個特征、屬性或指標決定的玄组,我們稱之為“關鍵碼”(Key)滔驾。關鍵碼本身也是一個對象。

實際上俄讹,作為優(yōu)先隊列的一個基本要求哆致,在關鍵碼之間必須能夠定義某種全序關系(Total orderrelation)。具體來說患膛,任何兩個關鍵碼都必須能夠比較大小摊阀,也就是滿足以下三條性質:

  • 自反性:對于任一關鍵碼k,都有k ≤ k
  • 反對稱性:若k1 ≤ k2 且k2 ≤ k1踪蹬,則k1 = k2
  • 傳遞性:若k1 ≤ k2 且k2 ≤ k3胞此,則k1 ≤ k3

不難證明,具有以上性質的關系實際上就是所有對象之間的一個線性次序跃捣。


三漱牵,條目與比較器

在給出優(yōu)先隊列的具體定義之前,還有兩個問題有待明確:

  • 在優(yōu)先隊列中疚漆,如何記錄和維護各對象與其關鍵碼之間的關聯(lián)關系酣胀?
  • 如何具體實現(xiàn)上面提出的全序關系,從而通過對象的比較能夠找出其中的最小者愿卸?

為了解決這兩個問題灵临,下面我們需要分別構造出條目和比較器這兩個類。

** 3.1 條目 **

所謂一個條目(Entry)趴荸,就是由一個對象及其關鍵碼合成的一個對象(面向對象程序設計的一種典型技巧??合成模式)儒溉,它反映和記錄了二者之間的關聯(lián)關系。這樣发钝,通過將條目作為優(yōu)先隊列的元素顿涣,即可記錄和維護原先對象與其關鍵碼之間的關聯(lián)關系。

定義條目的接口:

package dsa.PriorityQueue;

/*
* 條目接口
*/

public interface Entry {
    // 取條目的關鍵碼
    public Object getKey();

    // 修改條目的關鍵碼酝豪,返回此前存放的關鍵碼
    public Object setKey(Object k);

    // 取條目的數據對象
    public Object getValue();

    // 修改條目的數據對象涛碑,返回此前存放的數據對象
    public Object setValue(Object v);
}

默認條目類的實現(xiàn):

package dsa.PriorityQueue;

/*
* 默認條目
*/
public class EntryDefault implements Entry {
    protected Object key;
    protected Object value;

    /**************************** 構造函數 ****************************/
    public EntryDefault(Object k, Object v) {
        key = k;
        value = v;
    }

    /**************************** Entry接口方法 ****************************/
    // 取條目的關鍵碼
    public Object getKey() {
        return key;
    }

    // 修改條目的關鍵碼,返回此前存放的關鍵碼
    public Object setKey(Object k) {
        Object oldK = key;
        key = k;
        return oldK;
    }

    // 取條目的數據對象
    public Object getValue() {
        return value;
    }

    // 修改條目的數據對象孵淘,返回此前存放的數據對象
    public Object setValue(Object v) {
        Object oldV = value;
        value = v;
        return oldV;
    }
}

** 3.2 比較器 **
后一個問題似乎更難解決??畢竟蒲障,與C++之類的語言不同,Java 并不支持對比較操作符(">"、"<"或"=="等)的重載揉阎。

既然如此庄撮,不如基于Comparator 接口專門實現(xiàn)一個獨立于關鍵碼之外的比較器類,由它來確定具體的比較規(guī)則毙籽。在創(chuàng)建每個優(yōu)先隊列時洞斯,只要指定這樣一個比較器對象,即可按照該比較器確定的規(guī)則坑赡,在此后進行關鍵碼的比較烙如。這一策略的另一優(yōu)點在于,一旦不想繼續(xù)使用原先的比較器對象毅否,可以隨時用另一個比較器對象將其替換掉亚铁,而不用重寫優(yōu)先隊列本身。

定義Comparator接口:

package dsa.PriorityQueue;

/*
* 比較器接口
*/
public interface Comparator {
    public int compare(Object a, Object b);// 若a>(=或<)b螟加,返回正數刀闷、零或負數
}

默認情況下,我們將使用如下比較器:

package dsa.PriorityQueue;

/*
* Comparable對象的默認比較器
*/
public class ComparatorDefault implements Comparator {
    public ComparatorDefault() {
    }

    public int compare(Object a, Object b) throws ClassCastException {
        return ((Comparable) a).compareTo(b);
    }
}

四仰迁,優(yōu)先隊列ADT

優(yōu)先隊列 Q 基本操作

操作方法 功能描述
getSize() 返回Q 的規(guī)模
輸入:無
輸出:整數
isEmpty() 判斷Q 是否為空
輸入:無
輸出:布爾標志
getMin() 若Q 非空,則返回其中的最小條目(并不刪除)
輸入:無
輸出:條目
insert(k, obj) 將對象obj 與關鍵碼k 合成一個條目顽分,將其插入Q 中徐许,并返回該條目
輸入:一個對象及一個關鍵碼
輸出:條目
delMin() 若Q 非空,則從其中摘除關鍵碼最小的條目卒蘸,并返回該條目雌隅;否則,報錯
輸入:無
輸出:條目

insert()和delMin()則是優(yōu)先隊列特有的方法缸沃。需要強調的是恰起,** 這里允許在同一優(yōu)先隊列中出現(xiàn)具有相同關鍵碼的多個條目。**


五趾牧,優(yōu)先隊列java實現(xiàn)

定義接口:

package dsa.PriorityQueue;

/*
* 優(yōu)先隊列接口
*/
public interface PQueue {
    // 統(tǒng)計優(yōu)先隊列的規(guī)模
    public int getSize();

    // 判斷優(yōu)先隊列是否為空
    public boolean isEmpty();

    // 若Q非空检盼,則返回其中的最小條目(并不刪除);否則,報錯
    public Entry getMin() throws ExceptionPQueueEmpty;

    // 將對象obj與關鍵碼k合成一個條目翘单,將其插入Q中吨枉,并返回該條目
    public Entry insert(Object key, Object obj) 
            throws ExceptionKeyInvalid;

    // 若Q非空,則從其中摘除關鍵碼最小的條目哄芜,并返回該條目貌亭;否則,報錯
    public Entry delMin() throws ExceptionPQueueEmpty;
}

其中ExceptionPQueueEmpty和ExceptionKeyInvalid意外錯的定義:

package dsa.PriorityQueue;

/*
* 當試圖對空的優(yōu)先隊列應用getMin()或delMin()方法時认臊,本意外將被拋出
*/
public class ExceptionPQueueEmpty extends RuntimeException {
    public ExceptionPQueueEmpty(String err) {
        super(err);
    }
}
package dsa.PriorityQueue;

/*
* 當試圖使用非法關鍵碼時圃庭,本意外將被拋出
*/
public class ExceptionKeyInvalid extends RuntimeException {
    public ExceptionKeyInvalid(String err) {
        super(err);
    }
}

基于有序(非升)列表實現(xiàn)的優(yōu)先隊列:

package dsa.PriorityQueue;

import dsa.List.List;
import dsa.List.List_DLNode;
import dsa.Sequence.Sequence;
import other.Position;

/*
* 基于有序(非升)列表實現(xiàn)的優(yōu)先隊列
*/
public class PQueue_SortedList implements PQueue {
    private List L;
    private Comparator C;

    // 構造方法(使用默認比較器)
    public PQueue_SortedList() {
        this(new ComparatorDefault(), null);
    }

    // 構造方法(使用指定比較器)
    public PQueue_SortedList(Comparator c) {
        this(c, null);
    }

    // 構造方法(使用指定初始元素)
    public PQueue_SortedList(Sequence s) {
        this(new ComparatorDefault(), s);
    }

    // 構造方法(使用指定比較器和初始元素)
    public PQueue_SortedList(Comparator c, Sequence s) {
        L = new List_DLNode();
        C = c;
        if (null != s)
            while (!s.isEmpty()) {
                Entry e = (Entry) s.removeFirst();
                insert(e.getKey(), e.getValue());
            }
    }

    // 統(tǒng)計優(yōu)先隊列的規(guī)模
    public int getSize() {
        return L.getSize();
    }

    // 判斷優(yōu)先隊列是否為空
    public boolean isEmpty() {
        return L.isEmpty();
    }

    // 若Q非空,則返回其中的最小條目(并不刪除);否則,報錯
    public Entry getMin() throws ExceptionPQueueEmpty {
        if (L.isEmpty())
            throw new ExceptionPQueueEmpty("意外:優(yōu)先隊列空");
        return (Entry) L.last();
    }

    // 將對象obj與關鍵碼k合成一個條目剧腻,將其插入Q中拘央,并返回該條目
    public Entry insert(Object key, Object obj) throws ExceptionKeyInvalid {
        Entry entry = new EntryDefault(key, obj);// 創(chuàng)建一個新條目
        if (L.isEmpty()// 若優(yōu)先隊列為空
                || (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()))
                curPos = L.getPrev(curPos);// 不斷前移,直到第一個不小于entry的條目
            L.insertAfter(curPos, entry);// 緊接該條目之后插入entry
        }
        return (entry);
    }

    // 若Q非空恕酸,則從其中摘除關鍵碼最小的條目堪滨,并返回該條目;否則蕊温,報錯
    public Entry delMin() throws ExceptionPQueueEmpty {
        if (L.isEmpty())
            throw new ExceptionPQueueEmpty("意外:優(yōu)先隊列空");
        return (Entry) L.remove(L.last());
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末袱箱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子义矛,更是在濱河造成了極大的恐慌发笔,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凉翻,死亡現(xiàn)場離奇詭異了讨,居然都是意外死亡,警方通過查閱死者的電腦和手機制轰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門前计,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人垃杖,你說我怎么就攤上這事男杈。” “怎么了调俘?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵伶棒,是天一觀的道長。 經常有香客問我彩库,道長肤无,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任骇钦,我火速辦了婚禮宛渐,結果婚禮上,老公的妹妹穿的比我還像新娘眯搭。我一直安慰自己皇忿,他們只是感情好,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布坦仍。 她就那樣靜靜地躺著鳍烁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪繁扎。 梳的紋絲不亂的頭發(fā)上幔荒,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天糊闽,我揣著相機與錄音,去河邊找鬼爹梁。 笑死右犹,一個胖子當著我的面吹牛,可吹牛的內容都是我干的姚垃。 我是一名探鬼主播念链,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼积糯!你這毒婦竟也來了掂墓?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤看成,失蹤者是張志新(化名)和其女友劉穎君编,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體川慌,經...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡吃嘿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了梦重。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兑燥。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖琴拧,靈堂內的尸體忽然破棺而出贪嫂,到底是詐尸還是另有隱情,我是刑警寧澤艾蓝,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站斗塘,受9級特大地震影響赢织,放射性物質發(fā)生泄漏消返。R本人自食惡果不足惜狠毯,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望亭引。 院中可真熱鬧贞岭,春花似錦八毯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至芯侥,卻和暖如春泊交,著一層夾襖步出監(jiān)牢的瞬間乳讥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工廓俭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留云石,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓研乒,卻偏偏與公主長得像汹忠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子雹熬,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內容