数据结构-优先队列(最大优先队列、最小优先队列、索引优先队列)
由于队列先进先出的特性,无法灵活按照优先级进行数据的获取。但是在应用中又有此类需求,例如计算机按照优先级进行的任务调度。
所以需要对于队列进行改进,以满足此类需求
使用堆的数据结构,可以很方便的获取当前等待队列中的最大值或最小值,所以对于优先队列可以采用堆的方式完成
一、最大优先队列
每次都获取队列中的最大值
参考代码
package priority;
/**
* 实现最大优先队列
* <T extend Comparable> 代表泛型可以使用ComparerTo方法排序
* @param <T>
*/
public class MaxPriorityQueue<T extends Comparable> {
//存储堆中的元素
private T[] items;
//记录堆中元素的个数
private int N;
public static void main(String[] args) {
String[] arr = {"3","2","7","1", "6", "9"};
MaxPriorityQueue<String> maxPQ = new MaxPriorityQueue<>(arr.length);
for (String s : arr) {
maxPQ.insert(s);
}
System.out.println("堆大小:"+maxPQ.size());
String del = null;
while (!maxPQ.isEmpty()){
del = maxPQ.delMax();
System.out.print(del+",");
}
}
/**
* 初始化堆
*/
public MaxPriorityQueue(int capacity) {
//创建一个items元素数组
this.items = (T[]) new Comparable[capacity+1];
this.N = 0;
}
/**
* 获取队列中元素的个数
*/
public int size(){
return N;
}
/**
* 盘对队列是否为空
*/
public boolean isEmpty(){
return N == 0;
}
/**
* 判断堆中索引i处的元素是否小于索引j处的元素
* @param i 堆元素索引i
* @param j 堆元素索引j
* @return i是否小于j
*/
private boolean less(int i,int j){
return items[i].compareTo(items[j]) < 0;
}
/**
* 对堆中索引i处元素和索引j处元素进行交换
* @param i 堆元素索引i
* @param j 堆元素索引j
*/
private void exch(int i,int j){
T temp = items[i];
items[i] = items[j];
items[j] = temp;
}
/**
* 向堆中插入一个元素
* @param t 待插入的元素
*/
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);
items[N] = null;
N--;
sink(1);
return max;
}
/**
* 对插入元素导致堆无序,进行上浮操作
* 1)每次与k的父节点相比,如果小于其父节点,则需要交换位置
* @param k 造成无序的元素
*/
public void swim(int k){
//最多上浮的root根结点,即可退出while
while (k>1){
//k的父节点小于k,则需要调换两个结点的位置
if(less(k/2,k)){
exch(k/2,k);
}else{
break;
}
k = k/2;
}
}
/**
* 对删除堆顶元素后导致堆无序,需要进行下沉操作,保证顺序
* 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;
}
}
}
二、最小优先队列
每次都获取队列中的最小值
参考代码
package priority;
/**
* 最小优先队列
*/
public class MinPriorityQueue<T extends Comparable> {
private T[] items;
private int N;
public static void main(String[] args) {
String[] arr = {"3","2","7","1", "6", "9"};
MinPriorityQueue<String> minPQ = new MinPriorityQueue<>(arr.length);
for (String s : arr) {
minPQ.insert(s);
}
System.out.println("堆大小:"+minPQ.size());
String del = null;
while (!minPQ.isEmpty()){
del = minPQ.delMin();
System.out.print(del+",");
}
}
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;
}
/**
* 向堆中插入一个新元素
* @param t
*/
public void insert(T t){
items[++N] = t;
swim(N);
}
/**
* 删除堆中最小的元素
* @return 堆中最小的元素
*/
public T delMin(){
T min = items[1];
exch(1,N);
items[N] = null;
N--;
sink(1);
return min;
}
/**
* 较小元素上浮操作
* @param k
*/
private void swim(int k){
while (k>1){
if(less(k,k/2)){
exch(k,k/2);
}else{
break;
}
k = k/2;
}
}
/**
* 较大元素下沉操作
* @param k
*/
public void sink(int k){
int min = 0;
while (2*k<=N){
min = 2*k;
if(2*k+1<=N){
if(less(2*k+1,2*k)){
min = 2*k+1;
}
}
if(less(min,k)){
exch(min,k);
}
k = min;
}
}
}
三、索引优先队列(重点)
但是无论最大或最小优先队列,都无法通过索引获取其中的其他元素。故应用索引优先队列。该队列可以根据索引获取队列中的元素信息
- 为实现索引优先队列,我们可以在插入元素时将元素与一个索引相关联,之后便可以使用索引获取元素,但此时就无法保证堆的有序,因为如果要有序势必会移动元素,改变其索引位置。
- 所以需要创建一个辅助数组,存放元素的索引。形成一个逻辑堆,每次只对逻辑堆进行修改,移动其逻辑堆中实际元素的索引位置,这样便解决了问题
- 但是这样的话,我们无法通过逻辑堆的索引获取元素的值,因此当我们需要修改元素值时,只能一个一个比较要修改的元素找到其在堆中的位置
- 所以,我们仍需要一个数组来记录辅助数组与元素之前的关系,可以将新数组的索引作为辅助数组的值,辅助数组的值作为新数组的索引。这样我们可以根据新数组的索引快速定位到要修改的元素在堆中的位置
- 可以理解为堆中不存放具体的数据,而存放数据的引用
- 三个数组之间的关系如图
参考代码
package priority;
import java.util.Arrays;
/**
* 索引优先队列
* 由于最大或最小优先队列每次只能够获得队列中的
* 最大值或最小值,为了弥补其缺陷,设计了索引优先队列
* 思想:
* 建立辅助数组,对元素数组的索引进行移动,逻辑上移动
* 元素的位置,但同时也会造成查找元素不能使用索引,
* 故再次建立辅助数组的逆序数组(将辅助数组的下标与元素互换)
* 所有对于堆的操作其实都是对堆元素items数组的索引进行操作,即pq数组
*/
public class IndexMinPriorityQueue<T extends Comparable<T>> {
//存储堆中的元素
private T items[];
//存储元素在items数组中的索引,pq数组中需要堆有序
private int[] pq; //辅助数组
//保存pq的逆序,pq的值作为索引,pq的索引作为值
private int[] qp; //逆序数组
private int N;
public static void main(String[] args) {
String[] arr = {"3","2","7","1", "6", "9"};
IndexMinPriorityQueue<String> indexMinPQ = new IndexMinPriorityQueue(arr.length);
for (int i = 0; i < arr.length; i++) {
indexMinPQ.insert(i,arr[i]);
}
System.out.println("初始化后堆大小:"+indexMinPQ.size());
System.out.println("最小元素在items中的下标:"+indexMinPQ.minIndex());
indexMinPQ.changeItem(0,"5");
String minVal = null;
while (!indexMinPQ.isEmpty()){
minVal = indexMinPQ.delMin();
System.out.println("获取堆中最小值:"+minVal);
}
}
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;
}
}
/**
* 获取队列中元素个数
*/
public int size(){
return N;
}
/**
* 判断队列是否为空
*/
public boolean isEmpty(){
return N == 0;
}
/**
* 判断堆中索引pq[i]处元素是否小于索引pq[j]处的元素
* @param i pq数组的索引
* @param j pq数组的索引
* @return
*/
private boolean less(int i,int j){
//pq数组中存放的是元素在items数组中的索引
return items[pq[i]].compareTo(items[pq[j]])<0;
}
/**
* 交换堆中索引i和j处的值
* 实际上是移动的items数组的索引数组pq中的items的元素下标
* @param i pq[i]
* @param j pq[j]
*/
private void exch(int i,int j){
//交换堆元素的索引位置,保证堆有序
int temp = pq[i];
pq[i] = pq[j];
pq[j] = temp;
//更新qp数组中的值,存放pq数组的逆序
//qp的值为pq的索引,qp的索引为pq的值
qp[pq[i]] = i;
qp[pq[j]] = j;
}
/**
* 判断k对应的元素是否存在
* 由于pq[k]中存放items[]下标
* qp[k]中的k代表items[]下标,值代表pq[]索引下标
*/
public boolean contains(int k){
//默认情况下,qp中的元素为-1,当qp不为-1,则有元素
return qp[k] != -1;
}
/**
* 最小元素关联的索引
*/
public int minIndex(){
//pq数组是逻辑上的堆,最小元素就应该在其索引1处
return pq[1];
}
/**
* 向队列中插入一个元素,并关联items索引i
* @param i 要关联的索引
* @param t 待插入的元素
*/
public void insert(int i,T t){
//判断i处是否已经有元素,如果有则不让插入
if(contains(i)){
throw new RuntimeException("该索引已经存在");
}
/**
* 没有元素,则可以插入
* 将元素插入items[i]
* 将其下标放入逻辑堆pq[]
* 将pq的下标放入其逆序数组qp[]
*/
N++;
items[i] = t;
pq[N] = i;
qp[i] = N;
//上浮items[pq[N]],让pq堆有序
swim(N);
}
/**
* 获取并删除堆中最下的元素
* 1)从逻辑堆上获取要删除元素的下标,根据下标对应找到items中的元素
* 2)将最小元素获取后删除
* 3)将逻辑堆堆首元素与堆尾元素交换位置
* 4)删除逻辑堆中最后一个元素
* 5)对应删除逆序数组中item元素的下标
* 6)堆的大小-1
* 7)对新的堆首元素进行下沉操作,使堆重新有序
* @return 堆中最小的元素
*/
public T delMin(){
T minVal = items[pq[1]];
items[pq[1]] = null;
exch(1,N);
qp[pq[N]] = -1;
pq[N] = -1;
N--;
sink(1);
return minVal;
}
/**
* 删除索引i关联的元素
* 删除方法:
* 1)将索引i处的元素与堆的最后一个元素交换位置
* 2)删除逆序数组中记录的元素位置
* 3)删除pq[]中最后一个元素,即之前索引i处的元素
* 4)删除items中真正的元素
* 5)堆大小-1
* 6)对堆进行下沉操作,保证堆有序
* @param i 索引i
*/
public void delete(int i){
//找出i索引在pq[]中的索引,为了使删除后的堆重新有序
int k = qp[i];
exch(k,N);
//删除对应索引处的元素索引
qp[pq[N]] = -1;
pq[N] = -1;
items[i] = null;
N--;
//由于不清楚k所在堆中的具体位置,
// 与最后一个元素交换后可能需要下沉也可能需要上浮
sink(k);
swim(k);
}
/**
* 修改items中索引i处的元素为t
* @param i 待修改元素的索引
* @param t 新的元素值
*/
public void changeItem(int i,T t){
//修改真实元素
items[i] = t;
//获取逻辑堆中元素的下标
int k = qp[i];
//对逻辑堆进行下沉和上浮操作
sink(k);
swim(k);
}
/**
* 逻辑堆的上浮操作
* @param k
*/
public void swim(int k){
while (k > 1){
if(less(k,k/2)){
exch(k,k/2);
}else{
break;
}
k = k/2;
}
}
/**
* 逻辑堆的下沉操作
* @param k
*/
public void sink(int k){
int min = 0;
while (2*k<=N){
min = 2*k;
if(2*k+1<=N){
if(less(2*k+1,2*k)){
min = 2*k+1;
}
}
if(less(min,k)){
exch(min,k);
}
k = min;
}
}
}