[算法系列]优先队列,堆与堆排序
優先隊列,堆與堆排序
1 優先隊列
有時我們在處理有序元素時,并不一定要求他們全部有序. 很多情況下我們會收集一些元素, 處理當前最大的元素, 然后再收集更多元素, 再處理當前最大元素 …
這種情況下, 一個合適的數據結構一個支持兩種操作 : 刪除最大元素和 插入元素. 這種數據類型叫做 優先隊列.
我們可以使用有序或無序的數組或鏈表實現.使用無序序列是解決這個問題的 惰性方法, 我們僅在必要的時候才會找出最大元素(比如在pop的時候).
而使用有序序列是解決問題的積極方法, 在插入insert元素時就保持列表有序.
對于上述的對優先隊列的所有實現中 , 插入元素和刪除最大元素這兩個操作之一在最壞情況下需要線性時間完成:
- 有序數組插入元素是O(n)的, 刪除是O(1)
- 無序數組插入元素是O(1)的, 刪除是O(n)
而二叉堆能保證這兩種操作能夠更快地運行.
2 堆的定義與算法
2.1定義
二叉堆能夠很好地實現優先隊列的基本操作. 在二叉堆數組中,每個元素都要大于等于另外兩個特定位置的元素.(默認大頂堆)
- 定義堆有序: 當一個二叉樹的每個結點都大于等于它的兩個子結點時, 被稱為堆有序.
所以 不難得出根結點是堆有序的二叉樹中的最大結點
2.2 二叉堆表示法
- 定義二叉堆是一組能夠用堆有序的完全二叉樹排序的元素, 并在數組中按層級儲存(第一個位置不用)
以下二叉堆簡稱 堆 . 在一個堆中 , 位置k的 結點的父節點位置為 k/2 (整型類型, 小數不保留, 比如 5 的父節點在 2), 而它的兩個子節點的位置分別在 2k 和 2k+1.
由此我們可以通過計算數組的索引在樹中上下移動: a[k]向上一層就 k = k /2 ; 向左子樹就 k = 2k 或 2k +1
2.3堆的一些算法
我們用長度為N + 1 的數組來保存一個大小為N的堆, 我們不使用a[0] , 元素全放在a[1] ~ a[N]中
由下至上的堆有序swim( )
若某結點比他的父節點還要大, 則他需要通過交換他和他的父節點來修復堆. 但交換后的父節點可能比父節點的父節點還大 , 所以還得一遍遍地向上恢復.
private void swin (Comparable[] a , int k , int N){ //N 初始表示最后一個元素下標 N = a.length - 1 , 也是堆中實際元素個數while (k > 1 && less(a , k /2 , k)){exch(a , k/2 , k);k /= 2;}}由下至上的堆有序化sink( )
如果某結點比他的兩個子節點或者其中之一還要小 , 則需要通過將他和他的子節點中的較大值進行交換. 但交換后依然可能比子節點的子節點還小, 所以也得一遍遍地向下恢復
private void sink(Comparable[] a , int k , int N){ /* 向下遍歷, 但是只在前一半元素中考慮 */while ( 2 * k <= N){int j = 2 * k ;/* 如果他的右孩子比他大 , 而且也比左孩子大 , 則選擇與右孩子進行交換 */if( j < N && less(a , j, j + 1)) j ++;/* 如果此時k已經不小于他的孩子了 , 跳出循環 */if( ! less(a ,k , j )) break;/* 進行交換 */exch(a , k , j);k = j;}}向堆中插入一個元素v , 并且調整堆保持堆有序
public void intsert(Comparable[] a , Comparable v , int N){a[++N] = v;swim(N); }刪除堆中的最大元素,調整堆保持其有序
public Comparable delMax(Comparable[] a , int N ){/* 得到最大元素 */Comparable max = a[1];/* 和最后一個元素進行交換, 交換完后將N減1 */exch(a , 1 , N -- );a[N + 1] = null;sink(a , 1 , a.length) }2.4 堆排序
在上述構造了堆的數據結構后 , 堆排序就比較簡單了.
堆排序主要是兩個步驟:
-
將一個無序的數組a (還是默認a[0]不存值 )變得堆有序(成為一個堆) – 構造堆
-
將該最大元素與最后一個元素交換(交換后堆頂為最小元素) , 同時用sink將最小元素下沉, 用while對每個元素進行該步驟 – 交換+下沉排序
public void heapSort(Comparable[] a){int N = a.length - 1;/* 對前一半的元素進行sink下沉 , 得到一個堆 */for (int k = N / 2 ; K >= 1 ; k --)sink(a , k , N );/* 對每個元素進行操作 */while( N > 1){/* 交換最大的元素與最后一個元素 , 此時最大元素跑到a[N] , 然后N-- , 表示當前a[N]排序完成, */exch(a , 1 , N --);/* 上面的N--相當于從堆中拿出了排好的元素 , 現在把最小的元素沉下去, 當前堆頂又是當前最大值了 */sink(a , 1 , N );} }
附堆排序實現:
總結
以上是生活随笔為你收集整理的[算法系列]优先队列,堆与堆排序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 苹果三代耳机_浅谈华强北最强Airpod
- 下一篇: 面试重点:starter原理以及自己动手