数据结构(十二) -- 优先队列

一,优先队列

在决定病人接受治疗的次序时,除了他们到达医院的先后次序,更主要的将取决于病情的严重程度。由这类问题可以抽象出本章将要讨论的优先队列(Priority queue)结构。这一结构在很多应用领域都可以派上大用场,比如各种事件队列的模拟、操作系统中多任务的调度及中断机制、采用词频调整策略的输入法等。另外,优先队列也是很多高级算法的基础,比如Huffman编码、堆排序算法都要利用优先队列,而在采用空间扫描策略的算法中,优先队列是组织事件队列的最佳形式。

优先队列之所以具有广泛的用途,是得益于其高效率以及实现的简捷性。正如我们将要看到的,如果基于堆来实现优先队列,则其初始化构造可以在线性时间内完成,而每次插入或删除操作都可以在O(logn)的时间内完成。


二,关键码

正如医院根据病情严重程度确定病人接受治疗的次序一样,优先队列中各对象之间的次序是由它们共同的某个特征、属性或指标决定的,我们称之为“关键码”(Key)。关键码本身也是一个对象。

实际上,作为优先队列的一个基本要求,在关键码之间必须能够定义某种全序关系(Total orderrelation)。具体来说,任何两个关键码都必须能够比较大小,也就是满足以下三条性质:

  • 自反性:对于任一关键码k,都有k ≤ k
  • 反对称性:若k1 ≤ k2 且k2 ≤ k1,则k1 = k2
  • 传递性:若k1 ≤ k2 且k2 ≤ k3,则k1 ≤ k3

不难证明,具有以上性质的关系实际上就是所有对象之间的一个线性次序。


三,条目与比较器

在给出优先队列的具体定义之前,还有两个问题有待明确:

  • 在优先队列中,如何记录和维护各对象与其关键码之间的关联关系?
  • 如何具体实现上面提出的全序关系,从而通过对象的比较能够找出其中的最小者?

为了解决这两个问题,下面我们需要分别构造出条目和比较器这两个类。

** 3.1 条目 **

所谓一个条目(Entry),就是由一个对象及其关键码合成的一个对象(面向对象程序设计的一种典型技巧⎯⎯合成模式),它反映和记录了二者之间的关联关系。这样,通过将条目作为优先队列的元素,即可记录和维护原先对象与其关键码之间的关联关系。

定义条目的接口:

package dsa.PriorityQueue;

/*
* 条目接口
*/

public interface Entry {
    // 取条目的关键码
    public Object getKey();

    // 修改条目的关键码,返回此前存放的关键码
    public Object setKey(Object k);

    // 取条目的数据对象
    public Object getValue();

    // 修改条目的数据对象,返回此前存放的数据对象
    public Object setValue(Object v);
}

默认条目类的实现:

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 接口专门实现一个独立于关键码之外的比较器类,由它来确定具体的比较规则。在创建每个优先队列时,只要指定这样一个比较器对象,即可按照该比较器确定的规则,在此后进行关键码的比较。这一策略的另一优点在于,一旦不想继续使用原先的比较器对象,可以随时用另一个比较器对象将其替换掉,而不用重写优先队列本身。

定义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);
    }
}

四,优先队列ADT

优先队列 Q 基本操作

操作方法 功能描述
getSize() 返回Q 的规模
输入:无
输出:整数
isEmpty() 判断Q 是否为空
输入:无
输出:布尔标志
getMin() 若Q 非空,则返回其中的最小条目(并不删除)
输入:无
输出:条目
insert(k, obj) 将对象obj 与关键码k 合成一个条目,将其插入Q 中,并返回该条目
输入:一个对象及一个关键码
输出:条目
delMin() 若Q 非空,则从其中摘除关键码最小的条目,并返回该条目;否则,报错
输入:无
输出:条目

insert()和delMin()则是优先队列特有的方法。需要强调的是,** 这里允许在同一优先队列中出现具有相同关键码的多个条目。**


五,优先队列java实现

定义接口:

package dsa.PriorityQueue;

/*
* 优先队列接口
*/
public interface PQueue {
    // 统计优先队列的规模
    public int getSize();

    // 判断优先队列是否为空
    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;

/*
* 当试图对空的优先队列应用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);
    }
}

基于有序(非升)列表实现的优先队列:

package dsa.PriorityQueue;

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

/*
* 基于有序(非升)列表实现的优先队列
*/
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());
            }
    }

    // 统计优先队列的规模
    public int getSize() {
        return L.getSize();
    }

    // 判断优先队列是否为空
    public boolean isEmpty() {
        return L.isEmpty();
    }

    // 若Q非空,则返回其中的最小条目(并不删除);否则,报错
    public Entry getMin() throws ExceptionPQueueEmpty {
        if (L.isEmpty())
            throw new ExceptionPQueueEmpty("意外:优先队列空");
        return (Entry) L.last();
    }

    // 将对象obj与关键码k合成一个条目,将其插入Q中,并返回该条目
    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()))
                curPos = L.getPrev(curPos);// 不断前移,直到第一个不小于entry的条目
            L.insertAfter(curPos, entry);// 紧接该条目之后插入entry
        }
        return (entry);
    }

    // 若Q非空,则从其中摘除关键码最小的条目,并返回该条目;否则,报错
    public Entry delMin() throws ExceptionPQueueEmpty {
        if (L.isEmpty())
            throw new ExceptionPQueueEmpty("意外:优先队列空");
        return (Entry) L.remove(L.last());
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容