友情链接
前言
队列承担着很多软件中非常重要的的功能,由于其先进先出的性质让其非常适合用于消息或者进程的等待队列以及缓存功能。而进程的等待队列显然不能单纯使用先进先出的原理,这样的调度虽然简单,但是会使得后进入进程需要等待非常长的时间才能轮到其执行,并且有时候某些进程是紧急执行的,也就是说需要有优先级的差别,优先级高的自然也应该先执行。
因此本节将完成相关优先队列以及索引优先队列的构造,优先队列自然不必说,而优先索引队列是在优先队列的基础上增加了索引项,因为有些时候我们不需要返回一个对象,而是该元素的索引即可,用索引代指元素。
正文
注意: 本节以算法(三)复杂排序中的堆排序为基础,因为优先队列构造必然用到堆,所以请还不了解堆的读者先去看一下上一篇内容。
优先队列
优先队列适用于当给与数据非常庞大或者实时更新队列时的情况。因为优先队列采用堆排序,堆排序的特点即:不需要对所有元素排序即可获取到最大(最小)元素,并且如果堆已经构造完毕,每次删除堆顶节点时只需要O(logN)的复杂度即可让堆重新有序。这是非常节省资源的一种数据结构,当然,索引优先队列与索引队列的性质大致是一致的。
实现
优先队列构造没有除了堆的处理外没有任何其他困难操作,直接展示实现:(这里构造最小堆)
注意: 构造堆时可以不使用第0号元素,这里选择使用0号元素
public class MinPQ<Key extends Comparable<Key>>{
private Key[] pq;
private int N;
public MinPQ(int N){
pq = (Key[]) new Comparable[N];
this.N = 0;
}
//判断是否为空
public boolean isEmpty(){
return N==0;
}
//插入新元素
public void insert(Key v){
pq[N++] = v;
//上浮插入元素
swim(N-1);
}
//删除最小元素
public Key delMin(){
Key minKey = pq[0];
exchange(pq, 0, N-1);
N--;
//将删除元素设置为null
pq[N] = null;
//将交换后的0号元素下沉到正确位置
sink(0);
return minKey;
}
//下沉操作
public void sink(int m){
if(m>=this.N)return;
while((2*m+1)<this.N){
int j = 2*m+1;
if(j<N-1&&isLess(pq, j+1, j))j++;
if(isLess(pq,m,j))break;
exchange(pq, m, j);
m = j;
}
}
//上浮操作
public void swim(int m){
if(m>=this.N)return;
while(m<0&&isLess(pq, m, (m-1)/2)){
exchange(pq, m, (m-1)/2);
}
}
}
显然,delMin()
与insert()
方法都非常简单,但是构造的数据结构却非常实用。当遇到类似取出topN个元素时使用本数据结构非常高效。
索引优先队列
索引优先队列即在二叉堆中存的不再是插入元素,而是元素对应的索引,而索引优先队列最大的优势在于他可以通过索引获取或者删除当前队列中的元素。这样我们就可以对优先队列中的元素进行实时更新,这对于图的处理非常有意义,因为当构建最小生成树时或者使用最短路径的相关算法时,有时会出现加入当前节点后一些已经在优先队列中的节点失效,需要删除或者更改节点,这时索引优先队列发挥了巨大的灵巧性。
实现
索引优先队列最难点在于如何能够判断该索引节点是否在二叉堆中,显然遍历堆是一种方式,但是每次查询索引都需要遍历一边的话,时间耗费非常大,因此我们可以使用一个逆序数组,即将二叉堆数组的键值对调,可以通过索引来查询在二叉堆中的位置,并可以设置二叉堆中没有的元素的值为-1。
当然每次更新堆时需要同时更新逆序数组,但是这对时间复杂度并没有太大影响。
实现依然以最小堆为例。
补充: 如果索引不是单纯的数字,如长字符串等,则需要将所有数组都改为Map。
public class IndexMinPQ<Item extends Comparable<Item>>{
private int[] pq;
private int[] qp;
private int N;
private Item[] items;
public IndexMinPQ(int N){
this.N = 0;
this.pq = new int[N];
this.qp = new int[N];
this.items = (Item[])new Comparable[N];
for(int i=0;i<N;i++)this.qp[i] = -1;
}
//向优先队列中插入元素
public void insert(int k, Item v){
pq[N] = k;
qp[k] = N;
N++;
//swim有小改动,因为要同时更新qp数组
swim(N);
}
//删除最小元素,返回的是删除元素索引
public int delMin(){
int index = pq[0];
int lastIndex = pq[N-1];
exchange(pq, 0, N-1);
exchange(qp, index, lastIndex);
N--;
//sink有小改动,为了同时更新qp数组
sink(0);
qp[index] = -1;
items[index] = null;
return index;
}
//获取最小元素的内容
public Item min(){
return items[pq[0]];
}
//判断当前索引对应元素是否在队列中
public boolean contains(int k){
return qp[k]!=-1;
}
//更改存在元素的内容
public void change(int k, Item v){
if(!contains(k))return;
items[k] = v;
swim(qp[k]);
sink(qp[k]);
}
//删除指定索引的元素
public void delete(int k){
int nIndex = pq[N-1];
exchange(pq, N-1, qp[k]);
exchange(qp, k, nIndex);
N--;
swim(qp[k]);
sink(qp[k]);
items[k] = null;
qp[k] = -1;
}
}
当然从上也可以看出,可以只更改exchange函数来同时操作pq
和qp
两个数组,这样sink
以及swim
函数将不用变化。(这里不再重复sink
以及swim
函数)