算法——优先队列

许多应用程序都须要处理有序的元素,但不必定要求他们所有有序,或是不必定要一次就将它们排序。不少状况下咱们会收集一些元素,处理当前键值最大的元素,而后再收集更多的元素,再处理当前键值最大的元素,如此这般。例如,你可能有一台可以同时运行多个应用程序的电脑。这是经过每一个应用程序的事件分配一个优先级,并老是处理下一个优先级最高的事件来实现的。例如,绝大多数手机分配给来电的优先级都会比游戏程序高。java

通俗来讲,当收集元素永远不会中止,也就是至关于有无数个元素,那么咱们将永远比较不完全部元素,咱们也许只会使用其中最大的十个元素,因此咱们只须要把新进入队列的元素与这十个元素比较,去除最小的一个元素便可。算法

在这种状况下,一个合适的数据结构应该支持两种操做,删除最大元素插入元素。这种数据类型叫作有限队列。数组

优先队列是一种抽象数据类型,API以下(Key为泛型):数据结构

 

public class MaxPQ<Key extends Comparable<Key>>
MaxPQ()                                         建立一个优先队列       
    MaxPQ(int max)                            建立一个初始容量为max的优先队列
  MaxPQ(Key [] a)                            用a[]中元素建立一个优先队列
void insert(key v)                           向优先队列插入一个元素
Key max()                                        返回最大元素
  Key dekMax()                                删除并返回最大元素
boolean isEmpty()                        返回队列是否为空
int size()                                         返回优先队列中的元素个数

 

 

一个优先队列的用例:less

public class TopM{
	public static void main(String []args) {
		//打印输入流中的最大的M行
		int M=Integer.parseInt(args[0]);
		MinPQ<Transaction> pq=new MinPQ<Transaction>(M+1);
		while(StdIn.hasNextLine()) {
			//为下一行输入建立一个元素并放入优先队列中
			pq.insert(new Transaction(StdIn.readLine));
			if(pq.size()>M) {
				pq.delMin();//若是优先队列中存在M+1个元素则删除其中最小的元素
			}//最大的M的元素都在优先队列中
			Stack<Transaction> stack=new Stack<Transaction>();
			while(!pq,isEmpty) stack.push(pq.delMin());
			for(Transaction t:stack)StdOut.println(t);
		}
	}
}

分析:先从输入流获取一个整数M,将保存M个最大元素,以后不断从输入流中获取新的元素,与旧的M个元素比较,删除其中最小的一个元素,保证MinPQ中一直存有最大的M个元素。性能

初级实现spa

数组实现(无序):删除时,将待删除元素与边界元素互换,再删除;指针

数组实现(有序):按序排列,如删除最大,永远删除边界值;code

列表表示法:能够用基于链表的下压站的代码做为基础,逆序排列,pop()实现删除。排序

对比:实现栈和队列与实现优先队列最大的不一样在于对性能的要求。对于栈和队列,咱们的实现可以在常数时间实现内完成全部操做;而对于优先队列,咱们刚刚讨论过的全部初级实现中,插入元素和删除最大元素这两个操做之一在最坏状况下须要线性时间来完成,可是基于数据结构堆的实现可以保证这两种操做都能更快的执行(对数级别)。

堆的定义:在二叉树的数组中,每一个元素都要保证大于等于另两个特定位置的元素。在树中,即每一个节点都大于它的两个子叶。

二叉堆的表示:若是使用链表表示,每一个元素都须要三个指针;可是若是使用彻底二叉树,用数组就能够很容易表示。彻底二叉树存储在数组中,根结点存储在a[1],其余结点按照从上向下,从左到右的顺序依次存储,要注意的是a[0]不存储结点信息。

位置K的结点的父结点的位置为(k/2)向下取整,而它的两个子结点的位置分别为2K和2K+1;

一棵大小为N的彻底二叉树的高度为(lgN)向下取整。

堆的算法

堆的操做首先进行一些简单的改动,打破堆的状态,而后再遍历堆并按照要求将堆的状态恢复。咱们把这个过程叫作堆的有序化。

private boolean less(int i,int j){     //比较i和j的值
    return pq[i].compareTo(pq[j])<0;)
}
private void exch(int i,int j){
    Key t=pq[i];
    pq[i]=pq[j];
    pq[j]=t;
}

由下至上的对有序化(上浮swim):交换它和他的父结点来修复堆。

private void swim(int k){
    while(k>1&&less(k/2,k))
{
    exch(k/2,k);
    k=k/2;
}
}

由上至下的堆有序变化(下沉sink):交换它和它的两个子结点中较大者来交换恢复堆。

private void sink(int k){
    while(2*k<=N){
      int j=2*k;
      if(j<N&&less(j,j+1))j++;
      if(!less(k,j))break;
      each(k,j);
      k=j;
}
}

性能:对于一个含有N个元素的基于堆的有限队列,插入元素操做只须要不超过(lgN+1)次比较,删除最大元素的操做须要不超过2lgN次比较。便可在对数时间内完成。

改进:①多叉堆

            ②调整数组大小

            ③元素的不可变性

            ④索引优先序列

相关文章
相关标签/搜索