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

一,优先队列

在决定病人接受治疗的次序时,除了他们到达医院的先后次序,更主要的将取决于病情的严重程度。由这类问题可以抽象出本章将要讨论的优先队列(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());
    }
}
    原文作者:峰峰小
    原文地址: https://www.jianshu.com/p/0284e37b6028
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞