您的位置:首页 > 其它

优先队列之堆排序(一)

2017-11-24 15:08 369 查看

前言

为什么要是用优先队?优先队列又是什么?

许多应用程序都系要处理有序的元素,但不应定要求它们完全有序,或者不一定要一次就将它们排序。很多时候我们回收集一些元素,然后处处理当前键值最大的元素,然后再收集更多的元素,再狐狸当前键值最大的元素……举个生活中的例子吧,就像我们用的手机一样,手机里面有很多进程,但是有一个进程的优先级特别高,那就是来电显示(不然的话,放你在打游戏的时候也不会因为来电而退出,嘿嘿)。优先队列是局部有序的,它并不将所有的元素都排成成有序的。

基本函数

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的时候,算法效率比较好。

改进版的堆排序请参考

算法–优先队列之堆排序((二)升级版)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: