许多应用程序都需要处理有序的元素,但不一定要求他们全部有序,或者是不一定要以此就将他们排序。很多情况下我们会手机一些元素,处理当前键值最大的元素,然后再收集更多的元素,再处理当前键值最大的元素。如此这般。
在这种情况下,一个合适的数据结构应该支持两种操作:删除最大元素和插入元素。这种数据类型叫做优先队列(priority queue)。优先队列的使用和队列(删除最老的元素)以及栈(删除最新的元素)类似,但高效地实现该数据结构有一定的困难。
下文将简单的讨论优先队列的基本表现形式(其一或者两种操作都能在线性时间完成)。
2.4.1 API
优先队列是一种抽象数据类型(是一种能够对使用者隐藏数据表示的数据类型),它表示了一组值和对这些值的操作,它的抽象层使我们能够方便地将应用程序和我们学习的各种具体实现隔离开来。优先队列最重要的操作就是删除最大元素和插入元素,所以我们会把精力集中在它们身上。删除最大元素的方法名为delMax(),插入元素的方法名为insert()。通过less()来比较两个元素。如果允许重复元素,最大表示的是所有最大元素之一。为了将API定义完整,我们还需要加入构造函数和一个空队列测试方法。为了保证灵活性,我们在实现中使用了泛型,将实现Comparable接口的数据的类型作为参数Key。这使得我们可以不必再区别元素和元素的键,对数据类型和算法的描述也将更加清晰和简洁。
public class MaxPQ<Key extends Comparable<Key>> | |
MaxPQ() MaxPQ(int max) MaxPQ(Key[] a) void Insert(Key v) Key max() Key delMax() boolean isEmpty() int size() |
创建一个优先队列 创建一个初始容量为max的优先队列 用a[]中的元素创建一个优先队列 向优先队列中插入一个元素 返回最大元素 删除最大元素 返回队列是否为空 返回优先队列中的元素个数 |
表2.4.1 泛型优先队列的API
为了用例代码的方便,API包含的三个构造函数使得用例可以构造指定大小的优先队列(还可以用给定的一个数组将其初始化)。为了使用例代码更加清晰,我们会在适当的地方使用另一个类MinPQ。它和MaxPQ类似,只是含有一个delMin()方法来删除并返回队列中键值最小的那个元素。MaxPQ的任意实现都能很容易地转化为MinPQ的实现,反之亦然,只需要改变一下less()比较的方向即可。
优先队列的调用示例
考虑一下问题:输入N个字符串,每个字符串都对映着一个整数,你的任务就是从中找出最大的(或是最小的)M个整数(及其最小的)M个整数(及其关联的字符串)。解决这个问题的一种方法是将输入排序然后从中找出M个最大元素,但输入会非常庞大。另一种方法是将每个新的输入和已知的M个最大元素比较,但除非M较小,否则这种比较的代价会非常昂贵。只要我们能够高效地实现insert()和delMin(),下面的优先队列用例中调用了MinPQ的TopM就能使用优先队列解决这个问题,这就是目标。
示例 | 时间 | 空间 |
排序算法的用例 | NlogN |
N |
调用初级实现的优先队列 | NM |
M |
调用基于堆排序的优先队列 | NlogM |
M |
表2.4.2 N个输入中找到最大的M个元素成本
一个优先队列的用法
public class TopM { // This class should not be instantiated. private TopM() { } /** * Reads a sequence of transactions from standard input; takes a * command-line integer M; prints to standard output the M largest * transactions in descending order. */ public static void main(String[] args) { int M = Integer.parseInt(args[0]); MinPQ<Transaction> pq = new MinPQ<Transaction>(M+1); while (StdIn.hasNextLine()) { // Create an entry from the next line and put on the PQ. String line = StdIn.readLine(); Transaction transaction = new Transaction(line); pq.insert(transaction); // remove minimum if M+1 entries on the PQ if (pq.size() > M) pq.delMin(); } // top M entries are on the PQ // print entries on PQ in reverse order Stack<Transaction> stack = new Stack<Transaction>(); for (Transaction transaction : pq) stack.push(transaction); for (Transaction transaction : stack) StdOut.println(transaction); } }
从命令行输入一个整数M从输入流获得一系列字符串,输入流的每一行表示一个交易。这段代码调用了MinPQ并会打印数字最大的M行。它用到了Transaction类,构造了一个用数字作为键的优先队列。当优先队列的大小超过M时就删掉其中最小的元素。处理完所有交易,优先队列中存放着以曾旭排列的最大的M个交易,然后这段代码将他们放到一个栈中,遍历这个栈以点到他们的顺序,从而将他们按降序打印出来。