- 博主会对算法与数据结构会不断进行更新,敬请期待,如有什么建议,欢迎联系。
- 我们知道队列具有先进先出的特性,栈具有先进后出的特性,那么有没有一种数据结构可以根据自己的需求,以一定的规则从队列中弹出呢,优先队列就是实现这种目标的数据结构。一般情况下,计算机的任务都是有优先级的,我们需要从所有任务中找到优先级最高的任务优先执行,执行完毕后把这个任务从队列中移除。普通的队列需要完成这种要求,需要每次从队列中遍历所有元素找出最大值,效率不是很高,这个时候我们就可以采用一种数据结构来完成这种需求,那就是优先队列。在这里详细介绍了最大优先队列和最小优先队列以及他们的简单实现。
- 最大优先队列和最小优先队列每次弹出都是队列中的最大值或最小值,但是他们有一个缺点没有办法通过索引访问已存在优先队列中的索引对象,并更新他们,因此索引优先队列呼之欲出。索引优先队列可以更新队列中的任何一个元素,并通过下沉或上浮算法重新调整元素在队列中的顺序。
优先队列的实现细节:
- 优先队列是建立在堆的数据基础之上的;
- 最大优先队列头节点为最大值,在弹出最大值之前将头结点与尾结点进行交换,弹出最大值,然后再对头结点进行下沉操作;
- 最小优先队列头节点为最小值,在弹出最小值之前将头节点与尾结点进行交换,弹出最小值,然后再堆头节点进行下沉操作;
- 相对于最大、最小优先队列,索引优先队列成员变量中有三个数组,一个是用来储存元素的数组
items
,一个是保存每个元素在items数组中的索引,进行堆排序的数组queue
,最后一个是保存queue的逆序,queue的值作为索引,queue的索引作为值的reverseQueue
。
package com.victor.heap;
/**
* @description:
* @author: victor
*/
public class MaxPriorityQueue<T extends Comparable<T>> {
//存储堆中的元素
private T[] items;
//元素个数
private int N;
@SuppressWarnings("unchecked")
public MaxPriorityQueue(int capacity) {
this.items = (T[]) new Comparable[capacity + 1];
N = 0;
}
public MaxPriorityQueue(){
this(16);
}
public int size() {
return N;
}
public boolean isEmpty() {
return N == 0;
}
private boolean less(int i, int j) {
return items[i].compareTo(items[j]) < 0;
}
private void exchange(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);
}
/**
* 删除最大值
*
* @return 最大值
*/
public T deleteMax() {
T max = items[1];
exchange(1, N);
N--;
sink(1);
return max;
}
/**
* 下沉算法
*
* @param k 下沉的小标
*/
private void sink(int k) {
while (2 * k <= N) {
int max = 2 * k;
int right = max + 1;
if (right <= N)
max = less(max, right) ? right : max;
if (less(k, max))
exchange(k, max);
else
break;
k = max;
}
}
/**
* 上浮算法
*
* @param k 上浮的下标
*/
private void swim(int k) {
while (k > 1) {
if (less(k / 2, k))
exchange(k / 2, k);
else
break;
k = k / 2;
}
}
}
package com.victor.heap;
/**
* @description:
* @author: victor
*/
public class MinPriorityQueue<T extends Comparable<T>> {
private T[] items;
private int N;
@SuppressWarnings("unchecked")
public MinPriorityQueue(int capacity) {
this.items = (T[]) new Comparable[capacity + 1];
N = 0;
}
public MinPriorityQueue() {
this(16);
}
public boolean isEmpty() {
return N == 0;
}
private boolean less(int i, int j) {
return items[i].compareTo(items[j]) < 0;
}
private void exchange(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 deleteMin() {
T min = items[1];
exchange(1, N);
N--;
sink(1);
return min;
}
private void swim(int k) {
while (k > 1) {
if (less(k, k / 2))
exchange(k, k / 2);
else
break;
k = k / 2;
}
}
private void sink(int k) {
while (2 * k <= N) {
int min = 2 * k;
int right = min + 1;
if (right <= N)
min = less(min, right) ? min : right;
if (less(min, k))
exchange(min, k);
else
break;
k = min;
}
}
}
- 最大优先队列与最小优先队列还是比较简单的,基本与堆的实现类似,插入进行上浮算法,删除进行下沉算法。
package com.victor.heap;
import java.util.Arrays;
/**
* 索引优先队列
*
* @description: 下标最小优先队列
* @author: victor
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public class IndexMinPriorityQueue<T extends Comparable> {
/*用来储存元素的数组*/
private T[] items;
/*保存每个元素在items数组中的索引,进行堆排序的数组*/
private int[] queue;
/*保存queue的逆序,queue的值作为索引,queue的索引作为值*/
private int[] reverseQueue;
/*数组元素的个数*/
private int N;
public IndexMinPriorityQueue(int capacity) {
this.items = (T[]) new Comparable[capacity + 1];
this.queue = new int[capacity + 1];
this.reverseQueue = new int[capacity + 1];
//将reverseQueue的每个元素设置为-1
Arrays.fill(reverseQueue, -1);
}
/**
* 判断是否小于
*
* @param i queue的索引
* @param j queue的索引
* @return 是否i小于j
*/
private boolean less(int i, int j) {
return items[queue[i]].compareTo(items[queue[j]]) < 0;
}
/**
* 数组queue交换索引i,j
*
* @param i queue数组的索引i
* @param j queue数组的索引j
*/
private void exchange(int i, int j) {
int temp = queue[i];
queue[i] = queue[j];
queue[j] = temp;
//更新reverseQueue中的数据
reverseQueue[queue[i]] = i;
reverseQueue[queue[j]] = j;
}
/**
* 删除最小元素并返回该元素的索引
*
* @return 最小元素的索引
*/
public int delMin() {
int minIndex = queue[1];
exchange(1, N);
items[minIndex] = null; //将最小的值置空
reverseQueue[queue[N]] = -1;
queue[N] = -1;
N--;
sink(1);
return minIndex;
}
/**
* 往队列中插入一个元素并关联索引
*
* @param i 索引
* @param t 要插入的值
*/
public void insert(int i, T t) {
//如果此索引位置已存在,则不能插入
if (contains(i)) return;
//插入
items[i] = t;
N++;
//队列最后位置加入数组的索引
queue[N] = i;
reverseQueue[i] = N;
swim(N);
}
/**
* 上浮算法
*
* @param k 要上浮的索引
*/
private void swim(int k) {
while (k > 1) {
if (less(k, k / 2)) exchange(k, k / 2);
else break;
k = k / 2;
}
}
/**
* 下沉算法
*
* @param k 要下沉的索引
*/
private void sink(int k) {
while (2 * k <= N) {
int min = 2 * k;
int right = min + 1;
if (right <= N) min = less(min, right) ? min : right;
if (less(min, k)) exchange(min, k);
else break;
k = min;
}
}
/**
* 队列的尺寸
*
* @return 队列的尺寸
*/
public int size() {
return N;
}
/**
* 队列是否为空
*
* @return 是否为空
*/
public boolean isEmpty() {
return N == 0;
}
/**
* 是否包换k
*
* @param k 索引k
* @return 是否存在
*/
public boolean contains(int k) {
return reverseQueue[k] != -1;
}
/**
* 把与索引i有关的元素修改为t
*
* @param i 索引i
* @param t 要修改的元素为t
*/
public void changeItem(int i, T t) {
if (i >= N) {
insert(i, t);
return;
}
items[i] = t;
int k = reverseQueue[i];
swim(k);
sink(k);
}
/**
* 最小元素关联的索引
*
* @return 最小元素的索引
*/
public int minIndex() {
return queue[1];
}
/**
* 删除索引i对应的元素
*
* @param i 索引i
*/
public void delete(int i) {
int k = reverseQueue[i]; //索引i对应的队列索引
exchange(k, N); //交换k处的索引与最后的索引
items[i] = null;
reverseQueue[queue[N]] = -1; //必须先交换再设置为-1
queue[N] = -1;
N--;
//上浮和下沉都要做,因为不知道k是比较大还是比较小,但是,上浮或下沉只会成功一个
sink(k);
swim(k);
}
}
- 索引优先队列在删除一个元素的时候,一定要先和队列最后的元素进行交换后再进行删除,此时reverseQueue中的元素也进行了交换,再删除reverseQueue中queue[N]的元素。
- 删除之后上浮和下沉操作都要做,因为不知道交换后的元素是比较大还是比较小,但是上浮和下沉只会成功一个。