二叉堆是一種優先級隊列(priority queue)。搜索樹維護了所有數據結構的有序性,而在咱們不須要得知全局有序,僅僅須要全局的極值時,這樣是一種沒有必要的浪費。根據對象的優先級進行訪問的方式,稱爲循優先級訪問(call-by-priority)。優先級隊列自己並非一個隊列結構,而是根據優先級始終查找並訪問優先級最高數據項的數據結構。算法
首先,定義優先級隊列基類。須要支持的操做,主要是插入、獲取最大(最小)元素、刪除最大(最小)元素。api
1 template<typename T> struct PQ 2 { 3 virtual void insert(T) = 0; 4 virtual T getMax() = 0; 5 virtual T delMax() = 0; 6 };
徹底二叉堆數據結構
最爲經常使用的堆結構,就是徹底二叉堆。在邏輯形式上,結構必須徹底等同於徹底二叉樹,同時,堆頂意外的每一個節點都不大於(小於)其父親,即堆有序。spa
若是堆頂最大,稱爲最大二叉堆。反之,稱爲最小二叉堆。code
由於徹底二叉堆等同於徹底二叉樹的結構,故高度應當爲O(logn)。在前面二叉樹的一篇裏,提到了徹底二叉樹的層次遍歷結構能夠採用向量表示,更加簡潔方便、內存上更加緊湊。回顧一下,一個節點的秩爲n,它左孩子的秩爲2*n+1,右孩子爲2*n+2;當n!=0時,n的父親爲[n/2]-1(向上取整)。向量結構對於刪除添加等,分攤複雜度爲O(1)。對象
1 template<typename T> class PQ_ComplHeap: public PQ<T>, public Vector<T> 2 { 3 protected: 4 Rank percolateDown(Rank n, Rank i);//下濾 5 Rank percolateUp(Rank i);//上濾 6 void heapify(Rank n);//Floyd建堆算法 7 public: 8 PQ_ComplHeap() {}; 9 PQ_ComplHeap(T* A, Rank n) { copyFrom(A, 0, n); heapify(n); } 10 void insert(T);//容許重複故必然成功 11 T getMax(); 12 T delMax(); 13 };
查詢blog
優先級隊列最大的優點所在,始終維持全局最大(最小)值爲堆頂。所以只須要返回首元素便可。排序
1 template<typename T> T PQ_ComplHeap<T>::getMax() 2 { 3 return _elem[0]; 4 }
插入隊列
插入操做,首先把新元素加到向量末尾。由於須要維持堆序性,逐層檢查是否有序並作出調整,這個過程稱爲上濾。上濾的高度不超過樹高,所以複雜度不超過O(logn)。內存
1 template<typename T> void PQ_ComplHeap<T>::insert(T e) 2 { 3 Vector<T>::insert(e);//將新詞條接到向量末尾 4 percolateUp(_size - 1);//對該詞條進行上濾 5 } 6 template<typename T> Rank PQ_ComplHeap<T>::percolateUp(Rank i) 7 { 8 while (ParentValid(i)) 9 { 10 Rank j = Parent(i); 11 if (lt(_elem[i], _elem[j])) break; 12 swap(_elem[i], _elem[j]); i = j;//繼續向上 13 } 14 return i;//返回上濾最終抵達的位置 15 }
刪除
每次刪除操做,都將刪除堆頂。採用的策略,是把向量末尾的元素補充到堆頂,而後向下逐層比較,維持堆序性,與上濾對應地,稱爲下濾。
1 template<typename T> T PQ_ComplHeap<T>::delMax() 2 { 3 T maxElem = _elem[0]; 4 _elem[0] = _elem[--_size];//用末尾的值代替 5 percolateDown(_size, 0);//下濾 6 return maxElem;//返回備份的最大詞條 7 } 8 template<typename T> Rank PQ_ComplHeap<T>::percolateDown(Rank n, Rank i)//前n個詞條中的第i個進行下濾 9 { 10 Rank j; 11 while (i!= (j = ProperParent(_elem, n, i)))//i以及兩個孩子中,最大的不是i 12 { 13 swap(_elem[i], _elem[j]); i = j;//交換,並向下 14 } 15 return i;//返回下濾到達的位置 16 }
一樣,刪除操做除下濾外,均只須要常數時間,而下濾複雜度也不超太高度,故一樣爲O(logn)。
建堆
二叉堆一個重要的問題就是如何創建堆,一般是由一個向量結構進行建堆。最經常使用的方法有兩種,一種是逐個元素插入,而後對每一個元素都進行上濾。這樣操做的複雜度爲O(log1+log2+...+logk)=O(nlogn)。其實,整個過程的複雜度與對全局進行排序至關。
介紹一種更快的建堆方法,Floyd算法。算法核心思想是,把建堆的過程等效於堆的合併操做,即兩個堆H一、H2以及節點p的合併操做,此時,若是H1和H2已經符合堆序性,那麼只須要把p做爲H一、H2的父親,並對p進行下濾操做便可,最終成爲一個完整的堆。
1 template<typename T> void PQ_ComplHeap<T>::heapify(Rank n)//Floyd建堆法 2 { 3 for (int i = LastInternal(n); InHeap(n, i); i--)//自底向上下濾,從最後一個節點的父親開始 4 percolateDown(n, i); 5 }
僅對內部節點進行下濾便可。操做的複雜度爲O(n)。對比能夠發現,插入後上濾效率低的來源,是對每一個節點都進行了上濾,而Floyd方法對內部節點進行下濾,而徹底二叉堆中的外部節點數量多,而且深度小的節點遠遠少於高度小的節點。
堆排序
堆排序算法其實與選擇排序法很是類似。其核心思想都是將序列分爲先後兩個部分:未排序部分U、已經排序部分S。不一樣之處僅在於,如何從前半部分選擇極值元素。藉助堆結構,堆頂即爲最值,所以能夠很快的選擇出想要的元素,並與U部分的末尾交換便可,這也正是delMax()所作的工做。由於徹底二叉堆的該過程不超過O(logn),因此算法的複雜度爲O(nlogn),比選擇排序法的O(n^2)有了很大的提高。