优先队列
理论知识
MinPQ(最小优先队列)是一种常见的数据结构,用于有效管理一组元素,其中最小元素可以快速被检索和删除。这种数据结构广泛应用于各种算法中,包括图算法(如 Dijkstra 的最短路径算法和 Prim 的最小生成树算法)、事件驱动的模拟、调度任务等。
MinPQ 的核心操作
MinPQ 主要支持以下几种操作:
- 插入(Insert) - 将一个新元素添加到优先队列中。
- 查找最小(Find Minimum) - 获取优先队列中的最小元素,但不从队列中删除它。
- 删除最小(Delete Minimum) - 移除并返回优先队列中的最小元素。
- 判断是否为空(IsEmpty) - 检查优先队列是否为空。
- 大小(Size) - 返回优先队列中的元素数量。
MinPQ 的实现方式
MinPQ 可以用多种方式实现,其中最常见的是使用二叉堆(Binary Heap)结构,特别是最小堆。最小堆是一个完全二叉树,可以用数组来有效表示,具有以下性质:
- 每个节点的值都小于或等于其子节点的值。
- 树是完全填满的,除了最底层,最底层从左向右填充,直到填满。
使用最小堆的优势
使用最小堆实现 MinPQ 的优势在于其操作的高效性:
- 插入操作和删除最小操作的时间复杂度为 O(log n),其中 n 是队列中的元素数量。
- 查找最小操作的时间复杂度为 O(1),因为最小元素总是位于堆的根部。
应用示例
在许多实时系统和性能要求高的环境中,MinPQ 用来保证重要任务优先处理,或确保数据的最小值可以迅速访问。例如,在网络路由算法中,需要快速找到最短的未处理路径;在模拟环境中,优先处理最早发生的事件。
总的来说,MinPQ 是一种非常实用的数据结构,通过最小堆实现可以提供高效的性能,适用于需要快速访问和删除最小元素的各种应用场景。
实验数据
tinyPQ.txt测试数据内容
P Q E - X A M - P L E -
算法流程
代码实现
import edu.princeton.cs.algs4.StdIn;
import edu.princeton.cs.algs4.StdOut;
public class myMinPQ<Key extends Comparable<Key>> {
private Key[] pq;
private int n = 0;
public myMinPQ(int initN){
pq = (Key[]) new Comparable[initN+1];
}
public myMinPQ(){
this(1);
}
public myMinPQ(Key[] keys){
n = keys.length;
pq = (Key[]) new Comparable[n+1];
}
private void resize(int capacity) {
Key[] temp = (Key[]) new Comparable[capacity];
for (int i = 1; i <= n; i++) {
temp[i] = pq[i];
}
pq = temp;
}
public boolean isEmpty(){return n == 0;}
public int size(){return n;}
public void insert(Key v){
pq[++n] = v;
if (n == pq.length - 1) resize(2 * pq.length);
swim(n);
}
public Key delMin(){
Key min = pq[1];
exch(1,n--);
pq[n+1] = null;
sink(1);
if ((n > 0) && (n == (pq.length - 1) / 4)) resize(pq.length / 2);
return min;
}
private boolean greater(int i, int j){
return pq[i].compareTo(pq[j]) > 0;
}
private void exch(int i, int j){
Key t = pq[i]; pq[i]=pq[j]; pq[j]=t;
}
private void swim(int k){
while(k>1 && greater(k/2, k)) {
exch(k/2,k);
k=k/2;
}
}
private void sink(int k){
while(2*k <= n){
int j = 2*k;
if(j < n && greater(j, j+1)) j++;
if(!greater(k,j)) break;
exch(k,j);
k = j;
}
}
public static void main(String[] args) {
myMinPQ<String> pq = new myMinPQ<String>();
while (!StdIn.isEmpty()) {
String item = StdIn.readString();
if (!item.equals("-")) pq.insert(item);
else if (!pq.isEmpty()) StdOut.print(pq.delMin() + " ");
}
StdOut.println("(" + pq.size() + " left on pq)");
}
}
代码讲解
这段 Java 代码定义了一个名为 myMinPQ
的类,实现了一个最小优先队列(Min Priority Queue)。这个优先队列使用最小堆来维护元素的顺序,以确保能够快速地插入新元素并删除最小元素。下面是对这段代码各部分的详细解释:
类定义和泛型
myMinPQ<Key extends Comparable<Key>>
: 类定义使用泛型Key
,这意味着队列中的元素必须实现Comparable
接口,使得元素之间可以比较大小。
字段
private Key[] pq;
: 存储堆元素的数组。数组从索引1开始使用,以简化父节点和子节点的索引计算。private int n = 0;
: 表示堆中元素的数量。
构造函数
myMinPQ(int initN)
: 接受一个初始容量initN
并创建一个容量为initN + 1
的数组。myMinPQ()
: 无参构造函数,默认初始化容量为1。myMinPQ(Key[] keys)
: 接受一个数组keys
并用其初始化优先队列。
方法
resize(int capacity)
: 当数组容量不足以存储更多元素时,调用此方法以调整数组大小。isEmpty()
: 检查优先队列是否为空。size()
: 返回队列中的元素数量。insert(Key v)
: 向优先队列中插入一个新元素。使用swim
方法确保最小堆的属性。delMin()
: 从队列中删除并返回最小元素。使用sink
方法调整堆以维持最小堆的属性。greater(int i, int j)
: 比较堆中索引i
和j
的元素大小。exch(int i, int j)
: 交换堆中索引i
和j
的元素。swim(int k)
: 上浮操作,调整元素位置以维持堆的顺序。sink(int k)
: 下沉操作,调整元素位置以维持堆的顺序。
主函数
main(String[] args)
: 主函数从标准输入读取字符串,插入到优先队列中。如果读取到的字符串是"-"
并且队列不为空,则删除并打印最小元素。最终打印队列中剩余元素的数量。
这个类的实现利用了二叉堆的特性,确保每次插入和删除操作的时间复杂度为 O(log n),从而使得操作效率较高。通过调整数组大小的方式,该实现还可以动态地调整内存使用,以适应不同的使用场景。
实验
代码编译
$ javac myMinPQ.java
代码运行
$ java myMinPQ < data\tinyPQ.txt
E A E (6 left on pq)