二叉堆與堆排序

二叉堆是一種優先級隊列(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)有了很大的提高。

相關文章
相關標籤/搜索