一,优先队列
在决定病人接受治疗的次序时,除了他们到达医院的先后次序,更主要的将取决于病情的严重程度。由这类问题可以抽象出本章将要讨论的优先队列(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());
}
}