优先队列概述
普通的队列是一种先进先出(FIFO)的数据结构,元素在队列尾追加,而从队列头删除。在某些情况下,我们可能需要找出队列中的最大值或者最小值,例如使用一个队列保存计算机的任务,一般情况下计算机的任务都是有优先级的,我们需要在这些计算机的任务中找出优先级最高的任务先执行,执行完毕后就需要把这个任务从队列中移除。普通的队列要完成这样的功能,需要每次遍历队列中的所有元素,比较并找出最大值,效率不是很高,这个时候,我们就可以使用一种特殊的队列来完成这种需求,优先队列。
优先队列按照其作用不同,可以分为以下两种:
最大优先队列:
- 可以获取并删除队列中最人的值
最小优先队列: - 可以获取并删除队列中最小的值
最大优先队列
我们之前学习过堆,而堆这种结构是可以方便的删除最大的值,所以,接下来我们可以基于堆区实现最大优先队列。(优先队列底层是基于堆实现的。)
最大优先队列API设计
创建API
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;
}
//获取队列中元素的个数
public int size(){
return N;
}
//判断队列是否为空
public boolean isEmpty(){
return N==0;
}
//判断堆中索引i处的元素是否小于索引j处的元素
private boolean less(int i,int j){
return items[i].compareTo(items[j])<0;
}
//交换堆中i和j索引处的值
private void exch(int i,int j){
T tmp = items[i];
items[i] = items[j];
items[j] = tmp;
}
//往堆中插入一个元素
public void insert(T t){
items[++N] = t;
swim(N);
}
//删除堆中最大的元素,并返回这个值
/**
* 删除并获取堆顶元素
* 1)获取堆顶元素
* 2)将堆中最后一个元素移动到堆顶
* 3)删除堆中最后一个元素
* 4)利用下沉算法,让最后一个元素回到正确的位置
* 5)堆再次有序
* @return
*/
public T delMax(){
T max = items[1];
exch(1,N);
N--;
sink(1);
return max;
}
//使用上浮算法,使索引K处的元素能在堆中处于一个正确的位置
/**
* 对插入元素导致堆无序,进行上浮操作
* 1)每次与k的父节点相比,如果小于其父节点,则需要交换位置
* @param k 造成无序的元素
*/
private void swim(int k){
while (k > 1){
if(less(k/2,k)){
exch(k/2,k);
}
k /= 2;
}
}
//使用下沉算法,使索引K处的元素能在堆中处于一个正确的位置
/**
* 对删除堆顶元素后导致堆无序,需要进行下沉操作,保证顺序
* 1)找到k的左右子树
* 2)找出k左右子树的最大值
* 3)比较其与k的大小
* 4)如果大于k,则交换位置 (下沉)
* 5)如果不大于k,则无需操作
* @param k
*/
public void sink(int k){
int max = 0;
//判断k位置的元素具有左右子树
while (2*k<=N){
max = 2*k;
//判断是否有右子结点
if(2*k+1<=N){
if (less(2*k,2*k+1)){
max = 2*k+1;
}
}
//判断当前结点与其子结点中的最大值大小
if (less(k,max)){
exch(k,max);
}else{
break;
}
//将k移动到其最大子树上,继续向下判断
k = max;
}
}
}
创建主方法测试
public class MPQT {
public static void main(String[] args) {
MaxPriorityQueue<String> queue = new MaxPriorityQueue<>(10);
queue.insert("A");
queue.insert("B");
queue.insert("C");
queue.insert("D");
queue.insert("E");
queue.insert("F");
queue.insert("G");
while (!queue.isEmpty()){
String max = queue.delMax();
System.out.print(max + " ");
}
}
}
最小优先队列
基于上面最大优先队列的思路,我们可以得到最小优先队列的代码如下所示。
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;
}
//获取队列中元素的个数
public int size(){
return N;
}
//判断队列是否为空
public boolean isEmpty(){
return N==0;
}
//判断堆中索引i处的元素是否小于索引j处的元素
private boolean less(int i,int j){
return items[i].compareTo(items[j])<0;
}
//交换堆中i和j索引处的值
private void exch(int i,int j){
T tmp = items[i];
items[i] = items[j];
items[j] = tmp;
}
//往堆中插入一个元素
public void insert(T t){
items[++N] = t;
swim(N);
}
//删除堆中最大的元素,并返回这个值
public T delMax(){
T min = items[1];
exch(1,N);
items[N] = null;
N--;
sink(1);
return min;
}
//使用上浮算法,使索引K处的元素能在堆中处于一个正确的位置
private void swim(int k){
while (k > 1){
if(less(k,k/2)){
exch(k,k/2);
}else {
break;
}
k /= 2;
}
}
//使用下沉算法,使索引K处的元素能在堆中处于一个正确的位置
public void sink(int k){
int min = 0;
//判断k位置的元素具有左右子树
while (2*k<=N){
min = 2*k;
if(2*k+1<=N){
if (less(2*k,2*k+1)){
min = 2*k+1;
}
}
if (less(min,k)){
exch(min,k);
}else{
break;
}
k = min ;
}
}
}