普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在某些情况下,我们可能需要找出 队列中的最大值或者最小值,例如使用一个队列保存计算机的任务,一般情况计算机的任务都是有优先级的,我 们需要在这些计算机的任务中找出优先级最高的任务先执行,执行完毕后就需要把这个任务从队列中移除。普通的 队列要完成这样的功能,需要每次遍历队列中的所有元素,比较并找出最大值,效率不是很高,这个时候,我们就可以使用一种特殊的队列来完成这种需求,优先队列。

优先队列按照其作用不同,可以分为以下两种:
最大优先队列:
可以获取并删除队列中最大的值
最小优先队列:
可以获取并删除队列中最小的值
1.最大优先队列
我们之前学习过堆,而堆这种结构是可以方便的删除最大的值,所以,接下来我们可以基于堆区实现最大优先队列。大家如果不能理解的话,可以回到我前一篇发表的堆的实现原理相同
代码实现:
public class MaxPriorityQueue<T extends Comparable<T>> {
//存储堆中的元素
private T[] items;
//记录堆中元素的个数
private int N;
public MaxPriorityQueue(int capacity){
this.items=(T[]) new Comparable[capacity+1];
this.N=0;
}
// 判断堆中索引i处的元素是否小于索引j处的元素
private boolean less(int i,int j) {
return items[i].compareTo(items[j])<0;
}
// 交换元素
private void exch(int i,int j) {
T tmp=items[i];
items[i]=items[j];
items[j]=tmp;
}
// 判断堆中的元素是否为空
public boolean isEmpty() {
return this.N==0;
}
// 返回堆中元素个数
public int size() {
return this.N;
}
// 往堆中插入一个元素
public void insert(T t) {
items[++N]=t;
swim(N);//让元素t上浮
}
// 使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置
private void swim(int k) {
//如果已经到了根结点,就不需要循环了
while(k>1) {
//比较当前结点和其父结点
if(less(k/2,k)) {
//父结点小于当前结点,需要交换
exch(k/2,k);
}
k=k/2;
}
}
//删除堆中最大的元素,并返回这个最大元素
public T delMax() {
T max=items[1];
//交换当前结点和其父结点
exch(1,N);
//删除最后位置上的元素
items[N]=null;
//个数-1
N--;
sink(1);
return max;
}
//使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置
private void sink(int k) {
//如果当前已经是最底层了,就不需要循环了
while(2*k<=N) {
//找到子结点中的较大者
int max;
//若右子结点存在,找到子结点最大值
if(2*k+1<=N) {
if(less(2*k,2*k+1)) {
max=2*k+1;
}else {//右结点不存在,直接将左结点点赋值给最大值
max=2*k;
}
}else {
max=2*k;
}
//比较当前结点和子结点中的较大者,如果当前结点不小,则结束循环
if(!less(k,max)) {
break;
}
//当前结点小,则交换,
exch(k,max);
k=max;
}
}
}
测试类:
public class MaxPriorityQueueTest {
public static void main(String[] args) {
MaxPriorityQueue<String> queue=new MaxPriorityQueue<String>(8);
queue.insert("f");
queue.insert("g");
queue.insert("j");
queue.insert("c");
queue.insert("m");
queue.insert("a");
queue.insert("q");
queue.insert("t");
String s;
while(!queue.isEmpty()) {
s=queue.delMax();
System.out.print(s+"\t");
}
}
}
2.最小优先队列
最小优先队列实现起来也比较简单,我们同样也可以基于堆来完成最小优先队列。
我们前面学习堆的时候,堆中存放数据元素的数组要满足都满足如下特性:
1.
最大的元素放在数组的索引
1
处。
2.
每个结点的数据总是大于等于它的两个子结点的数据
其实我们之前实现的堆可以把它叫做最大堆,我们可以用相反的思想实现最小堆,让堆中存放数据元素的数组满足
如下特性:
1.
最小的元素放在数组的索引
1
处。
2.
每个结点的数据总是小于等于它的两个子结点的数据。
代码实现:
public class MinPriorityQueue<T extends Comparable<T>> {
//存储堆中的元素
private T[] items;
//记录堆中元素的个数
private int N;
// 构造方法
public MinPriorityQueue(int capacity) {
this.items=(T[]) new Comparable[capacity+1];
this.N=0;
}
//判断堆中索引i处的元素是否小于索引j处的元素
private boolean less(int i,int j) {
return items[i].compareTo(items[j])<0;
}
// 交换元素
private void exch(int i,int j) {
T tmp=items[i];
items[i]=items[j];
items[j]=tmp;
}
// 判断堆中的元素是否为空
public boolean isEmpty() {
return this.N==0;
}
// 返回堆中元素个数
public int size() {
return this.N;
}
// 往堆中插入一个元素
public void insert(T t) {
items[++N]=t;
swim(N);
}
//使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置
private void swim(int k) {
//如果没有父结点,则不再上浮
while(k>1) {
//如果当前结点比父结点小,则交换
if(less(k,k/2)) {
exch(k,k/2);
}
k=k/2;
}
}
//删除堆中最小的元素,并返回这个最小元素
public T delMin() {
//索引1处的值是最小值
T min=items[1];
//交换索引1处和索引N处的值
exch(1,N);
//删除索引N处的值
items[N]=null;
//数据元素-1
N--;
//对索引1处的值做下沉,使堆重新有序
sink(1);
//返回被删除的值
return min;
}
//使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置
private void sink(int k) {
//如果没有子结点,则不再下沉
while(2*k<=N) {
//找出子结点中的较小值的索引
int min;
if(2*k+1<=N) {
if(less(2*k,2*k+1)) {
min=2*k;
}else {
min=2*k+1;
}
}else {
min=2*k;
}
//如果当前结点小于子结点中的较小值,则结束循环
if(!less(min,k)) {
break;
}
//当前结点大,交换
exch(min,k);
k=min;
}
}
}
测试代码:
public class MaxPriorityQueueTest {
public static void main(String[] args) {
MaxPriorityQueue<String> queue=new MaxPriorityQueue<String>(8);
queue.insert("f");
queue.insert("g");
queue.insert("j");
queue.insert("c");
queue.insert("m");
queue.insert("a");
queue.insert("q");
queue.insert("t");
String s;
while(!queue.isEmpty()) {
s=queue.delMax();
System.out.print(s+"\t");
}
}
}
3.索引优先队列
在之前实现的最大优先队列和最小优先队列,他们可以分别快速访问到队列中最大元素和最小元素,但是他们有一 个缺点,就是没有办法通过索引访问已存在于优先队列中的对象,并更新它们。为了实现这个目的,在优先队列的 基础上,学习一种新的数据结构,索引优先队列。接下来我们以最小索引优先队列举列。
3.1索引优先队列实现思路
步骤一:
存储数据时,给每一个数据元素关联一个整数,例如
insert(int k,T t),
我们可以看做
k
是
t
关联的整数,那么我们的实 现需要通过k
这个值,快速获取到队列中
t
这个元素,此时有个
k
这个值需要具有唯一性。
最直观的想法就是我们可以用一个
T[] items
数组来保存数据元素,在
insert(int k,T t)
完成插入时,可以把
k
看做是 items数组的索引,把
t
元素放到
items
数组的索引
k
处,这样我们再根据
k
获取元素
t
时就很方便了,直接就可以拿到 items[k]即可。
步骤二:
步骤一完成后的结果,虽然我们给每个元素关联了一个整数,并且可以使用这个整数快速的获取到该元素,但是, items数组中的元素顺序是随机的,并不是堆有序的,所以,为了完成这个需求,我们可以增加一个数组
int[]pq,
来 保存每个元素在items
数组中的索引,
pq
数组需要堆有序,也就是说,
pq[1]
对应的数据元素
items[pq[1]]
要小于等 于pq[2]
和
pq[3]
对应的数据元素
items[pq[2]]
和
items[pq[3]]
。

步骤三:
通过步骤二的分析,我们可以发现,其实我们通过上浮和下沉做堆调整的时候,其实调整的是
pq
数组。如果需要 对items
中的元素进行修改,比如让
items[0]=“H”,
那么很显然,我们需要对
pq
中的数据做堆调整,而且是调整 pq[9]中元素的位置。但现在就会遇到一个问题,我们修改的是
items
数组中
0
索引处的值,如何才能快速的知道需 要挑中pq[9]
中元素的位置呢?
最直观的想法就是遍历
pq
数组,拿出每一个元素和
0
做比较,如果当前元素是
0
,那么调整该索引处的元素即可, 但是效率很低。
我们可以另外增加一个数组,
int[] qp,
用来存储
pq
的逆序。例如:
在
pq
数组中:
pq[1]=6;
那么在
qp
数组中,把
6
作为索引,
1
作为值,结果是:
qp[6]=1;
当有了
pq
数组后,如果我们修改
items[0]="H"
,那么就可以先通过索引
0
,在
qp
数组中找到
qp
的索引:
qp[0]=9, 那么直接调整pq[9]
即可。
代码实现:
public class IndexMinPriorityQueue<T extends Comparable<T>> {
//存储堆中的元素
private T items[];
//保存每个元素在items数组中的索引,pq数组需要堆有序
private int pq[];
//保存qp的逆序,pq的值作为索引,pq的索引作为值
private int qp[];
//记录堆中元素的个数
private int N;
//构造方法
public IndexMinPriorityQueue(int capacity) {
this.items=(T[]) new Comparable[capacity+1];
this.pq=new int[capacity+1];
this.qp=new int[capacity+1];
this.N=0;
for(int i=0;i<qp.length;i++) {
qp[i]=-1;
}
}
//获取队列中元素的个数
public int size() {
return this.N;
}
//判断队列是否为空
public boolean isEmpty() {
return this.N==0;
}
//判断堆中索引i处的元素是否小于索引j处的元素
private boolean less(int i,int j) {
return items[pq[i]].compareTo(items[pq[j]])<0;
}
//交换堆中i索引和j索引处的值
private void exch(int i,int j) {
//先交换pq数组中的值
int tmp=pq[i];
pq[i]=pq[j];
pq[j]=tmp;
//更新qp数组中的值
qp[pq[i]]=i;
qp[pq[j]]=j;
}
//判断k对应的元素是否存在
public boolean contains(int k) {
return qp[k]!=-1;
}
//最小元素关联的索引
public int minIndex() {
//pq的索引1处,存放的是最小元素在items中的索引
return pq[1];
}
//往队列中插入一个元素,并关联索引i
public void insert(int i,T t) {
//如果索引i处已经存在了元素,则不让插入
if(contains(i)) {
throw new RuntimeException("该索引已经存在");
}
//个数+1
N++;
//把元素存放到items数组中
items[i]=t;
//使用pq存放i这个索引
pq[N]=i;
//在qp的i索引处存放N
qp[i]=N;
//上浮items[pq[N]],让pq堆有序
swim(N);
}
//删除队列中最小的元素,并返回该元素关联的索引
public int delMin() {
//找到items中最小元素的索引
int minIndex=pq[1];
//交换pq中索引1处的值和N处的值
exch(1,N);
//删除qp中索引pq[N]处的值
qp[pq[N]]=-1;
//删除pq中索引N处的值
pq[N]=-1;
//删除items中的最小元素
items[minIndex]=null;
//元素数量-1
N--;
//对pq[1]做下沉,让堆有序
sink(1);
return minIndex;
}
//删除索引i关联的元素
public void delete(int i) {
//找出i在pq中的索引
int k=qp[i];
//把pq中索引k处的值和索引N处的值交换
exch(k,N);
//删除qp中索引pq[N]处的值
qp[pq[N]]=-1;
//删除pq中索引N处的值
pq[N]=-1;
//删除items中索引i处的值
items[k]=null;
//元素数量-1
N--;
//对pq[k]做上浮,让堆有序
swim(k);
//对pq[k]做下沉,让堆有序
sink(k);
}
//把与索引i关联的元素修改为为t
public void changeItem(int i,T t) {
//修改items数组中索引i处的值为t
items[i]=t;
//找到i在pq中的位置
int k=qp[i];
//对pq[k]做上浮,让堆有序
swim(k);
//对pq[k]做下沉,让堆有序
sink(k);
}
//使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置
private void swim(int k) {
//如果已经到了根结点,则结束上浮
while(k>1) {
//比较当前结点和父结点,如果当前结点比父结点小,则交换位置
if(less(k,k/2)) {
exch(k,k/2);
}
k=k/2;
}
}
//使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置
private void sink(int k) {
//如果当前结点已经没有子结点了,则结束下沉
while(2*k<=N) {
//找出子结点中的较小值
int min;
if(2*k+1<=N) {
if(less(2*k,2*k+1)) {
min=2*k;
}else {
min=2*k+1;
}
}else {
min=2*k;
}
//如果当前结点的值比子结点中的较小值小,则结束下沉
if(less(k,min)) {
break;
}
exch(min,k);
k=min;
}
}
}