前言
为什么要是用优先队?优先队列又是什么?
许多应用程序都系要处理有序的元素,但不应定要求它们完全有序,或者不一定要一次就将它们排序。很多时候我们回收集一些元素,然后处处理当前键值最大的元素,然后再收集更多的元素,再狐狸当前键值最大的元素……举个生活中的例子吧,就像我们用的手机一样,手机里面有很多进程,但是有一个进程的优先级特别高,那就是来电显示(不然的话,放你在打游戏的时候也不会因为来电而退出,嘿嘿)。优先队列是局部有序的,它并不将所有的元素都排成成有序的。
基本函数
- Key delmax()用来删除队列中最大的元素
- void insert(Key value)用来插入新元素
- boolean isEmpty() 用来判断队列是否为空
- int getSize() 返回 队列中的元素个数
- Key getMax() 返回队列中的最大值元素
我们用二叉堆表示法来实现优先队列,首先来介绍一下什么是二叉堆
在一个二叉树里面,如果每个结点都大于等于它的两个子结点的时候,那么那它就是堆有序的;二叉堆是一组能够用堆有序的完全二叉树(若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。)排序的元素,并在数组中按照层级存储(为了表示方便,我们不使用数组中的第一个元素)。所以根节点的地方是队列中键值最大的元素。
函数具体实现(采用java语言)
堆实现的比较和交换算法
private boolean less(int i,int j) {
return pq[i] <pq[j];
}
private void exch(int i,int j) {
int temp = pq[i];
pq[i]=pq[j];
pq[j]=temp;
}
由下至上的堆有序化—Swim(上浮)
private void swim(int k) { // 上浮 元素最大比较次数为logN+1次 (也就是二叉树为满二叉树的时候,再往里面插入元素的时候)
while(k>1&&less(k/2,k)) {
exch(k/2,k);
k=k/2;
}
}
由上至下的堆有序化—Sink(下沉)
private void sink(int k) {
//下沉 一共循环logN次 元素比较次数为2*logN次
while(2*k<=N) {
int j=2*k;
if(j<N && less(j,j+1)) {
j++;
}
if(!less(k,j)) {
break;
}
exch(k,j);
k=j;
}
}
插入函数和删除最大元素最大函数
public void insert(int elem) {
pq[++N]=elem;
swim(N);
}
public int delMax() {
int max=pq[1];
exch(1, N--);//交换根节点和最后一个节点,并删除交换后的最后一 个节点 也就是之前的根节点
//pq[N+1] = (Integer) null ;//防止对象游离 基本类型元素不可用
sink(1);;//恢复堆得有序性
return max;
}
基于堆的优先队全部代码
package 优先队列1;
public class PriorityQueue {//堆顶元素最大
private int[] pq;
private int N = 0; //存储于pq[1...N]中,pq[0]没有使用
private boolean less(int i,int j) {
return pq[i] <pq[j];
}
private void exch(int i,int j) {
int temp = pq[i];
pq[i]=pq[j];
pq[j]=temp;
}
public PriorityQueue(int length) {
this.pq=new int[length+1]; //下标为0的元素不用 元素从下标为1的地方开始
}
private void swim(int k) {
// 上浮 元素最大比较次数为logN+1次 (也就是二叉树为满二叉树的时候,再往里面插入元素的时候)
while(k>1&&less(k/2,k)) {
exch(k/2,k);
k=k/2;
}
}
private void sink(int k) {
//下沉 一共循环logN次 元素比较次数为2*logN次
while(2*k<=N) {
int j=2*k;
if(j<N && less(j,j+1)) {
j++;
}
if(!less(k,j)) {
break;
}
exch(k,j);
k=j;
}
}
public int getElemSize()
{
return N;
}
public boolean isEmpty() {
return N == 0;
}
public void insert(int elem) {
pq[++N]=elem;
swim(N);
}
public int delMax() {
int max=pq[1];
exch(1, N--);//交换根节点和最后一个节点,并删除交换后的最后一个节点 也就是之前的根节点
//pq[N+1] = (Integer) null ;//防止对象游离 基本类型元素不可用
sink(1);;//恢复堆得有序性
return max;
}
private void display() {
for(int i=1;i<=N;++i)
System.out.print(pq[i] + " ");
System.out.println();
}
public static void main(String[] args) {
// TODO Auto-generated method stub
PriorityQueue PQ = new PriorityQueue(12);
PQ.insert(12);
PQ.insert(15);
PQ.insert(2);
PQ.insert(18);
PQ.insert(7);
PQ.insert(7);
System.out.println("The count of elem is "+PQ.getElemSize());
System.out.print("All elem are :");
PQ.display();
System.out.println("Now,the max elem in priorityQueue is "+PQ.delMax());
System.out.println("Now,the max elem in priorityQueue is "+PQ.delMax());
System.out.println("Now,the max elem in priorityQueue is "+PQ.delMax());
}
}
运行结果如下:
算法分析
- sink()方法中,一共循环logN次 元素最大比较次数为2*logN次
- swim()方法中, 元素最大比较次数为logN+1次,也就是二叉树为满二叉树的时候,再往里面插入元素的时候。
- 算法的分析 用图表表示为:
其中, - 第一行unordered array是没有排序的数组,插入和删除还有求最大元素的算法复杂度分别为1,n,n。
- 第二行ordered array是排好序的数组,插入和删除还有求最大元素的算法复杂度分别为n,1,1。
- 第三行binary heap是二叉堆,插入和删除还有求最大元素的算法复杂度分别为log n,2*log n,1。
- 第四行d-ary heap是d叉堆(也就是每个结点有d个子结点),插入和删除还有求最大元素的算法复杂度分别为logd n,d*logd n,1
- sweet spot: d = 4 意思就是说最好的情况是当d=4的时候,算法效率比较好。
改进版的堆排序请参考