算法——優先隊列

許多應用程序都須要處理有序的元素,但不必定要求他們所有有序,或是不必定要一次就將它們排序。不少狀況下咱們會收集一些元素,處理當前鍵值最大的元素,而後再收集更多的元素,再處理當前鍵值最大的元素,如此這般。例如,你可能有一臺可以同時運行多個應用程序的電腦。這是經過每一個應用程序的事件分配一個優先級,並老是處理下一個優先級最高的事件來實現的。例如,絕大多數手機分配給來電的優先級都會比遊戲程序高。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次比較。便可在對數時間內完成。

改進:①多叉堆

            ②調整數組大小

            ③元素的不可變性

            ④索引優先序列

相關文章
相關標籤/搜索