优先队列1
之前的堆中有提及过优先队列,链接:https://blog.youkuaiyun.com/qq_32193775/article/details/104115268
前言
许多应用程序都需要处理有序的元素,但不一定要求它们全部有序,例如,绝大多数手机分配给来电的优先级都会比游戏程序的高;
在这种情况下一个合适的数据结构应该支持两种操作,删除最大元素和插入元素,这种数据类型叫做优先队列,优先队列的使用和队列(删除最老的元素)以及栈(删除最新的元素)类似,但是它需要有个权值,高效的实现它更有挑战;
重要操作:
- delMax() 删除最大元素
- insert() 插入元素
- less() 辅助函数,比较大小
api:
MaxPQ() | 创建优先队列 |
---|---|
MaxPQ(int max) | 创建初始容量为max的优先队列 |
MaxPQ(Key[] a) | 用a[] 的元素创建一个优先队列 |
void insert(Key k) | 优先队列中插入元素 |
Key max() | 返回最大元素 |
Key delMax() | 删除最大元素 |
Boolean isEmpty() | 返回队列是否为空 |
int size() | 返回优先队列中元素个数 |
同时也存在delMin(),MinPQ()等方法;
初级实现
- 数组实现(无序):最简单的方式,基于栈,要实现删除最大元素,可以添加一段类似于选择排序的内循环代码,将最大元素与边界元素交换,然后删除它,和栈中pop()类似;
- 数组实现(有序):insert()中添加代码,将所有较大的元素向右移动一格,以使数组保持有序(插入排序类似),这样,最大的元素在数组的一端,删除最大的元素就和pop()类似了;
堆实现
数据结构二叉堆能够很好的实现优先队列的基本操作;
复习:堆有序:一颗二叉树,每个结点都大于等于它的两个子结点;
完全二叉树可以用数组表示,具体方法如:根结点在index=1,它的子结点在2,3处,子结点的子结点在位置4,5,6,7处;
故,位置k的结点的父结点为k/2处,其子结点分别在2k,2k+1处;
堆的算法
我们用长度为N+1的数组pq[]来表示一个大小为N的堆,我们不会使用pq[0],重要的操作就是如何实现这个堆的有序化;下列代码为辅助函数
private boolean less(int i, int j){
return pq[i].compareTo(pq[j])<0;
}
private void exch(int i,int j){
Key t = qp[i];
qp[i] = qp[j];
qp[j] = t;
}
由下至上的堆有序化(上浮)
如果堆的有序状态,因为某个结点变得比它的父结点更大而被打破,那么就需要交换它和它的父结点来修复堆;
即判断它和k/2的大小,若大,则交换;但这个结点依然可能比新的父结点大,需要循环;
代码如下:
private void swim(int k){
while(k>1&&less(k/2,k)){
exch(k/2,k);
k=k/2;
}
}
由上至下的堆有序(下沉)
同上浮,因为某个结点变得比它的两个子结点或其中之一更小而被打破了;那么需要同上浮方法来修复堆;代码如下:
private void sink(int k){
while(2*k<=N){//N为当前优先队列的大小
int j = 2*k;
if(j<N&&less(j,j+1)) j++;//找到两个子结点中的最大值;
if(!less(k,j)) break;
exch(k,j);
k=j;
}
}
基于堆(数组表示,完全二叉树)的优先队列
public class MaxPQ<Key extends Comparable<Key>>{
private Key[] pq;
private int N=0;//size
public MaxPQ(int max){
pq=(Key[]) new Comparable[max+1];
}
public int size(){
return N;
}
public void insert(Key v){
pq[++N]=v;
swim(N);
}
public Key delMax(){
Key max = pq[1];
excha(1,N--);
pq[N+1] = null;
sink(1);
return max;
}
//注意其他方法见前述;
}
堆排序
我们
可以把任意优先队列变成一种排序方法,将所有元素插入一个查找最小元素的优先队列;随后重复调用删除最小元素的操作来将它们按顺序删除(同样也可以删除最大元素),用基于堆的优先队列这样做是一种全新的排序方法吗----堆排序;
为保证和上述代码一致,所有堆排序采用删除最大元素来进行;
代码如下:
public static void sort(Comparable[] a){
int N = a.length;
for(int k = N/2 ; k>=1;k--){
sink(a,k,N);
}
while(N>1){
exch(a,1,N--);
sink(a,1,N);
}
}
代码解释:
exch和sink方法稍有改动,
for循环中将该数组做到堆有序;
随后可以已知,根结点为所有元素最大值,故需要先将根结点和末尾交换位置,根结点固定了,n-1长度的数组不有序,所有要下沉(因根结点为小值导致);故而调用sink方法;
从而达到了排序的效果;
时间复杂度:2logN;