1 优先队列概述
● 普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在某些情况下,我们可能需要找出队列中的最大最小值,例如使用一个队列保存计算机的任务,一般情况下计算机的任务都是有优先级的,我们需要在这些计算机的任务中找出优先级最高的任务先执行,执行完毕后就需要把这个任务从队列中移除。
● 普通队列要完成这样的功能,需要每次遍历队列中的所有元素,比较并找出最大值,效率不是很高,这个时候我们就可以使用一种特殊的队列来完成这个需求,优先队列
2 最大优先队列
2.1 概述
● 由于堆这种结构可以方便的删除最大的值,所以我们使用基于堆这种数据结构来实现最大优先队列
2.2 代码实现
public class MaxPriorityQueue<T extends Comparable<T>> {
// 定义一个数组,存储数据
private T[] items;
// 计算数组中的个数
private int N;
// 构造方法,初始化数组和N
public MaxPriorityQueue(int capacity) {
items = (T[]) new Comparable[capacity + 1];
N = 0;
}
// 返回数组元素个数
public int size() {
return N;
}
// 判断数组是否为空
public boolean isEmpty() {
return N == 0;
}
// 判断堆中索引i处的元素是否小于j处的索引
public boolean less(int i, int j) {
return items[i].compareTo(items[j]) < 0;
}
// 交换元素
public 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 max = items[1];
exch(1, N);
N--;
sink(1);
return max;
}
// 使用上浮算法,将插入进去的元素调整到合适的位置
private void swim(int k) {
// 判断是否到根节点,如果到根节点则结束循环
while (k > 1) {
// 判断父节点是否小于该节点,如果小于该节点则进行交换
if (less(k / 2, k)) {
exch(k / 2, k);
}
k = k / 2;
}
}
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
max = 2 * k + 1;
}
}
// 判断子节点和该节点那个节点较大
if (!less(k,max)){
// 如果该节点较大,则结束循环
break;
}
// 如果该节点较小,则继续循环比较
exch(k,max);
k = max;
}
}
}
3 最小优先队列
● 把最小的元素放在数组的索引1处
● 每个节点的数据总是小于等于它的两个子节点的数据
● 代码实现
public class MinPriorityQueue {
public class MaxPriorityQueue<T extends Comparable<T>> {
// 定义一个数组,存储数据
private T[] items;
// 计算数组中的个数
private int N;
// 构造方法,初始化数组和N
public MaxPriorityQueue(int capacity) {
items = (T[]) new Comparable[capacity + 1];
N = 0;
}
// 返回数组元素个数
public int size() {
return N;
}
// 判断数组是否为空
public boolean isEmpty() {
return N == 0;
}
// 判断堆中索引i处的元素是否小于j处的索引
public boolean less(int i, int j) {
return items[i].compareTo(items[j]) < 0;
}
// 交换元素
public 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 delMin() {
T max = items[1];
exch(1, N);
N--;
sink(1);
return max;
}
// 使用上浮算法,将插入进去的元素调整到合适的位置
private void swim(int k) {
// 判断是否到根节点,如果到根节点则结束循环
while (k > 1) {
// 判断该节点是否小于父节点,如果小于父节点则进行交换
if (less(k, k/2)) {
exch(k, k/2);
}
k = k / 2;
}
}
private void sink(int k) {
// 判断是否到最后一层,如果到最后一层则结束循环
while (2 * k <= N) {
// 定义一个临时变量,储存两个子节点中最小的索引值
int min = 2 * k;
// 判断该节点是否有右节点
if (2 * k + 1 <= N) {
// 如果有右节点,则判断左节点小还是右节点小
if (less(2 * k+1, 2 * k )) {
// 如果右节点小,则将右节点的索引值赋值给min
min = 2 * k + 1;
}
}
// 判断子节点和该节点那个节点较小
if (less(k,min)){
// 如果该节点较小,则结束循环
break;
}
// 如果该节点较大,则继续循环比较
exch(min,k);
k = min;
}
}
}
}
4 索引优先队列
4.1 概述
● 最小和最大优先队列可以快速的获取到最大和最小的值,但是无法通过索引访问优先队列中的对象
● 而索引优先队列则可以通过缩影快速的访问到队列中的值
4.2 索引优先队列实现思路
● 步骤一
○ 创建一个item数组来保存数据元素,我们可以通过insert(int k,T t)将索引存储在数组中,可以通过k值获取队列中的数据元素
● 步骤二
○ 当我们把数据元素根据索引存放到数组中时,此时数据元素在数组中是无序的,并不是堆排序
○ 此时,我们需要增加一个数组pq,来保存items数组中的索引值,pq数组需要根据items元素值进行堆排序
● 步骤三
○ 当我们进行上浮和下沉调整的时,实际上是调整pq数组,当我们需要对items数组中的元素进行修改时,pq数组需要重新进行排序。
○ 当我们修改items[0]处的值时,我们需要去pq数组中寻找0在pq数组的那个位置,然后对其进行上浮或下沉调整,我们可以通过遍历pq数组找出0在pq数组的那个位置然后对其进行修改,可这样效率较低
○ 我们可以创建一个新的数组qp来保存pq数组的逆序,这样当我们需要修改items中的元素时,我们可以先通过qp数组获取到pq数组中的位置,然后对其进行上浮或下沉的调整,这样就减少了遍历pq数组的过程
4.3 代码实现
import javax.swing.plaf.nimbus.AbstractRegionPainter;
public class IndexMinPriorityQueue<T extends Comparable<T>> {
private T[] items;
private int[] pq;
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];
N = 0;
for (int i = 0; i < qp.length; i++) {
qp[i] = -1;
}
}
// 判断堆pq中i处索引的值是否小于j处索引的值
private boolean less(int i, int j) {
// 先通过pq找出items中的索引值,然后在找出items中的元素进行比较
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;
}
// 删除队列中最小的值
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;
}
// 向队列插入元素
public void insert(int i, T t) {
// 判断要插入的索引处是否有值,如果有则不能插入
if (contains(i)) {
throw new RuntimeException("该索引已经存在");
}
// 个数+1
N++;
// 把元素存放到items中
items[i] = t;
// 将索引i存放到pq数组中
pq[N] = i;
// 将N存放在qp数组的i处
qp[i] = N;
// 使用上浮算法,让pq有序
swim(N);
}
// 上浮算法
private void swim(int k) {
while (k > 1) {
// 比较当前节点和父节点,如果当前节点比父节点小,则交换位置
if (less(k, k / 2)) {
exch(k, k / 2);
}
k = k / 2;
}
}
// 下沉算法
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 int size() {
return N;
}
// 判断队列是否为空
public boolean isEmpty() {
return N == 0;
}
// 判断k对应的元素是否为空
public boolean contains(int k) {
// 在初始化时,qp中的值为-1,如果插入了元素,那么qp中的值就不是-1
return qp[k] != -1;
}
// 把索引i关联的元素改为t
public void changeItem(int i, T t) {
// 修改items数组中的元素索引处的值为t
items[i] = t;
//通过qp找到i在pq中的位置
int k = qp[i];
// 对堆进行调整使其有序
sink(k);
swim(k);
}
// 最小元素关联的索引
public int minIndex() {
return pq[1];
}
// 删除索引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[i] = null;
//元素-1
N--;
// 对堆进行调整,使其有序
sink(k);
swim(k);
}
}