普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在某些情况下,我们可能需要找出 队列中的最大值或者最小值,例如使用一个队列保存计算机的任务,一般情况下计算机的任务都是有优先级的,我们需要在这些计算机的任务中找出优先级最高的任务先执行,执行完毕后就需要把这个任务从队列中移除。普通的 队列要完成这样的功能,需要每次遍历队列中的所有元素,比较并找出最大值,效率不是很高,这个时候,我们就可以使用一种特殊的队列来完成这种需求,优先队列。
优先队列按照其作用不同,可以分为以下两种:
最大优先队列: 可以获取并删除队列中最大的值
最小优先队列: 可以获取并删除队列中最小的值
-- 最大优先队列:
我们之前学习过堆,而堆这种结构是可以方便的删除最大的值,所以,接下来我们可以基于堆区实现最大优先队 列。
-- 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 temp = items[i];
items[i] = items[j];
items[j] = temp;
}
// 往堆中插入一个元素
public void insert(T t) {
items[++N] = t;
// 使用上浮算法
swim(N);
}
// 删除堆中最大的元素,并返回这个最大的元素
public T delMax() {
// 保留要删除的元素,用于返回
T max = items[1];
// 交换索引1处和索引N处的值
exch(1,N);
// 删除堆中最后一个元素
items[N] = null;
N--;
// 使用下次算法
sink(1);
return max;
}
// 使用上浮算法,使得索引k处的元素在堆中处于正确的位置
private void swim(int k) {
// 如果已经到了根结点,就不再需要循环了
while (k > 1) {
// 比较当前结点大于其父结点的值,则交换位置
if(less(k/2,k)) {
exch(k/2,k);
}
// 更换k的位置
k = k / 2;
}
}
// 使用下沉算法,使得索引k处的元素在堆中处于正确的位置
private void sink(int k) {
// 如果当前结点已经没有子结点,循环结束
while (2 * k <= N) {
// 找到子结点中,较大的那个值,先假定左子结点为较大的那个值
int max = 2 * k;
if(2 * k + 1 <= N) { // 如果存在右子结点
if(less(2 * k,2 * k + 1)) {
max = 2 * k + 1;
}
}
// 如果当前结点,比子节点中的较大值小,跳出循环
if(!less(k,max)) {
break;
}
// 交换当前索引和比较大子结点的位置
exch(k,max);
k = max;
}
}
}
-- 测试代码:
public class MaxPriorityQueueTest {
public static void main(String[] args) throws Exception {
String[] arr = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"};
MaxPriorityQueue<String> maxpq = new MaxPriorityQueue<>(20);
for (String s : arr) {
maxpq.insert(s);
}
System.out.println(maxpq.size());
String del;
while(!maxpq.isEmpty()){
del = maxpq.delMax();
System.out.print(del+",");
}
}
}
-- 运行效果图:
-- 最小优先队列:
最小优先队列实现起来也比较简单,我们同样也可以基于堆来完成最小优先队列。 我们前面学习堆的时候,堆中存放数据元素的数组要满足都满足如下特性:
1.最大的元素放在数组的索引1处。
2.每个结点的数据总是大于等于它的两个子结点的数据。
其实我们之前实现的堆可以把它叫做最大堆,我们可以用相反的思想实现最小堆,让堆中存放数据元素的数组满足 如下特性:
1.最小的元素放在数组的索引1处。
2.每个结点的数据总是小于等于它的两个子结点的数据。
-- API设计:
-- 代码:
/**
* 最小优先队列
*/
public class MinPriorityQueue<T extends Comparable<T>> {
// 存储堆中的元素
private T[] items;
// 记录堆中元素的个数
private int N;
// 构造方法
public MinPriorityQueue(int capacity) {
items = (T[]) new Comparable[capacity+1];
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 temp = items[i];
items[i] = items[j];
items[j] = temp;
}
// 往堆中插入元素
public void insert(T t) {
items[++N] = t;
// 使用上浮算法
swim(N);
}
// 删除堆中最小的元素,并返回
public T delMin() {
// 保留要删除的元素用于返回
T min = items[1];
// 交换索引1处和索引N的元素
exch(1,N);
// 删除索引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);
}
// 变换k的位置
k = k / 2;
}
}
// 使用下沉算法,使得索引k处的元素在堆中处于正确的位置
private void sink(int k) {
// 如果当前结点没有子结点啦,循环就不需要执行了
while (2 * k <= N) {
// 找出子结点中较小的那个元素
int min = 2 * k;
if(2 * k + 1 <= N && less(2 * k + 1,2 * k)) {
min = 2 * k + 1;
}
// 如果当前结点,已经不小于子结点中较小的那个值,则跳出循环
if(less(k,min)) {
break;
}
// 交换索引k和索引min
exch(k,min);
// 变换索引k的位置
k = min;
}
}
}
-- 测试代码:
public class MinPriorityQueueTest {
public static void main(String[] args) throws Exception {
String[] arr = {"S", "O", "R", "T", "E", "X", "A", "M", "P", "L", "E"};
MinPriorityQueue<String> minpq = new MinPriorityQueue<>(20);
for (String s : arr) {
minpq.insert(s);
}
System.out.println(minpq.size());
String del;
while(!minpq.isEmpty()){
del = minpq.delMin();
System.out.print(del+",");
}
}
}
-- 运行效果图:
--索引优先队列
实现思想,用items作为内存保存值,其中索引是键用于关联里面的值。pq数组存放的是items中的索引,让堆有序,也就是说pq就是真实的堆,items只是保存值的内存空间。qp数组保存items索引(关联值的键)在堆中的位置,这样就可以快速定位items索引(关联值的键)在堆中的位置。 最终效果如下图:
-- 代码:
public class IndexMinPriorityQueue <T extends Comparable<T>> {
// 存储堆中的元素
private T[] items;
// 存储items中的元素(关联的键),堆有序
private int[] pq;
// 存储item索引(关联的键),在堆中的位置
private int[] qp;
// 堆中元素的个数
private int N;
// 构造方法
public IndexMinPriorityQueue(int capacity) {
items = (T[]) new Comparable[capacity+1];
pq = new int[capacity+1];
qp = new int[capacity+1];
// 默认情况下堆中不存储任何值
for(int i = 0;i < qp.length;i++) {
qp[i] = -1;
}
}
// 获取队列中元素的个数
public int size() {
return N;
}
// 判断队列是否为空
public boolean isEmpty() {
return 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) {
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() {
return pq[1];
}
// 往队列中插入一个元素
public void insert(int i,T t) {
// 如果i索引已经关联过,则不让插入
if(contains(i)) {
throw new RuntimeException("该索引已经存在");
}
// 将元素放到内存中
items[i] = t;
// 将关联索引放到堆中
pq[++N] = i;
// 记录关联索引在堆中的位置
qp[i] = N;
// 使用上浮算法
swim(N);
}
// 删除堆中最小的元素,并返回该元素关联的索引
public int delMin() {
// 找到items中最小元素的索引
int minIndex = pq[1];
// 交换索引1和索引N处的值
exch(1,N);
// 删除qp中pq[N]处的值
qp[pq[N]] = -1;
// 删除pq索引N处的值
pq[N] = -1;
// 删除items的最小元素
items[minIndex] = null;
// 个数-1
N--;
// 使用下沉算法,让堆有序
sink(1);
return minIndex;
}
// 删除索引i关联的元素
public void delete(int i) {
// 找出i在pq中的索引
int k = qp[i];
// 交换索引k和索引N位置处的值
exch(k,N);
// 删除qp索引pq[N]处的值
qp[pq[N]] = -1;
// 删除pq索引N处的值
pq[N] = -1;
// 删除items中索引i处的值
items[i] = null;
// 对pq做下沉算法
sink(k);
// 对pq做上浮算法
swim(k);
}
// 把索引i处关联的元素修改为t
public void changeItem(int i,T t) {
items[i] = t;
// 找到索引i在pq中的位置
int k = qp[i];
// 对pq做下沉算法
sink(k);
// 对pq做上浮算法
swim(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 = 2 * k;
if(2 * k + 1 <= N && less(2 * k + 1,2 * k)) {
min = 2 * k + 1;
}
// 如果当前结点比较小的子结点小,结束下沉
if(less(k,min)) {
break;
}
exch(k,min);
k = min;
}
}
}
-- 测试代码:
public class IndexMinPriorityQueueTest {
public static void main(String[] args) {
String[] arr = {"S", "O", "R", "T", "E", "X", "A", "M", "P", "L", "E"};
IndexMinPriorityQueue<String> indexMinPQ = new IndexMinPriorityQueue<>(20);
//插入
for (int i = 0; i < arr.length; i++) {
indexMinPQ.insert(i,arr[i]);
}
System.out.println(indexMinPQ.size());
//获取最小值的索引
System.out.println(indexMinPQ.minIndex());
//测试修改
indexMinPQ.changeItem(0,"Z");
int minIndex=-1;
while(!indexMinPQ.isEmpty()){
minIndex = indexMinPQ.delMin();
System.out.print(minIndex+",");
}
}
}
-- 运行效果:
@ 以上内容属于个人笔记