咱們常常會碰到下面這種狀況,並不須要將全部數據排序,只須要取出數據中最大(或最小)的幾個元素,如排行榜。java
那麼這種狀況下就可使用優先隊列,優先隊列是一個抽象數據類型,最重要的操做就是刪除最大元素和插入元素,插入元素的時候就順便將該元素排序(實際上是堆有序,後面介紹)了。編程
二叉堆實際上是優先隊列的一種實現,下面主要講的是用數組實現二叉堆。數組
先上一個實例:ide
若有一個數組A{9,7,8,3,0,6,5,1,2}ui
用二叉樹來表示數組更直觀:spa
從這張圖咱們能夠總結一些規律:3d
上面這三點應該很是好理解code
下面就引出一個問題,怎樣讓一個數組變成堆有序呢? blog
首先,須要介紹兩個操做: 排序
當插入一個結點,或改變一個結點的值時,上浮指的是交換它和它的父節點以達到堆有序
在上面的堆有序的圖中,若是咱們把0換成10,那麼上浮的操做具體爲:
(1)10比它的父節點7大,因此交換
(2)交換後,10比它的父節點9還要打,交換
以後獲得的二叉樹以下圖:
代碼以下(須要注意,下標是從1開始,A[0]保留不用,如下全部代碼相同):
//index based on 1 public void swim(Integer[] a,Integer key) { while(key > 1 && a[key/2] < a[key]) { change(a,key/2,key); key /= 2; } }
2. 由上至下的堆有序化(下沉)
由上浮能夠很容易得出下沉的概念:
當插入一個結點,或改變一個結點的值時,下沉指的是交換它和它的較大子節點以達到堆有序。
在原來的二叉樹中,若是將根節點9換成4,操做以下:
(1)4與它的最大子節點8交換位置
(2)4與它的最大子節點6交換位置
交換後的二叉樹以下圖:
代碼以下:
//index based on 1 public void sink(Integer[] a,Integer key) { Integer max = key*2; while(key*2 < a.length - 1) { if(a[key*2] < a[key*2 + 1]) { max = key*2 + 1; } else { max = key*2; } if(a[key] > a[max]) break; change(a,key,max); key = max; } }
那麼將一個數組構形成有序堆,相應的也有兩種方法:使用上浮以及使用下沉:
初始數組以下:
Integer[] a = {null,2,1,5,9,0,6,8,7,3};
上浮構造有序堆:
從數組左邊到右邊依次使用上浮,由於根節點A[1]沒有父節點,因此從A[2]開始:
public void buildBinaryHeapWithSwim(Integer[] a) { for(int k=2;k<a.length;k++) { swim(a,k); } }
結果以下:
a: [null ,9 ,7 ,8 ,5 ,0 ,2 ,6 ,1 ,3] 讀者有興趣能夠本身畫一下二叉樹,看是否有序
下沉構造有序堆:
代碼: public void buildBinaryHeapWithSink(Integer[] a) { //index based on 1 for(int k=a.length/2;k>=1;k--) { sink(a,k); } }
爲何使用下沉只須要遍歷數組左半邊呢?
由於對於一個數組,每個元素都已是一個子堆的根節點了,sink()對於這些自對也適用。若是一個結點的兩個子節點都已是有序堆了,那麼在該結點上調用sink(),可讓整個數組變成有序堆,這個過程會遞歸的創建起有序堆的秩序。咱們只須要掃描數組中一半的元素,跳過葉子節點。
a: [null ,9 ,7 ,8 ,3 ,0 ,6 ,5 ,1 ,2]
能夠看到使用下沉和上浮構造出來的有序堆並不相同,那麼用哪個更好呢?
答案是使用下沉構造有序堆更好,構造一個有N個元素的有序堆,只需少於2N次比較以及少於N次交換。
證實過程就略過了。
前面說了那麼多,終於要說到堆排序了,其實前面的優先隊列和二叉堆都是爲了堆排序作準備。
如今咱們知道若是將一個數組構形成有序堆的話,那麼數組中最大的元素就是有序堆的根節點。
那麼很容易想到一個排序的思路:
第一種:將數組構形成有序堆,將根節點拿出來,即將A[1]拿出(由於A[0]不用,固然也可使用,讀者能夠本身編程實現),對剩下的數組再構造有序堆……
不過第一種思路只能降序排列,而且須要構造一個數組用來存放取出的最大元素,以及最大的弊端是取出最大元素後,數組剩下的其它全部元素須要左移。
那麼第二種辦法就能夠避免以上的問題:
第二種:先看圖:
先來解釋下這幅圖:
…….
這樣循環下去,即獲得按升序排序的數組
代碼:
public void heapSort(Integer[] a) { for(int k=a.length/2;k>=1;k--) { sink(a,k); } Integer n = a.length - 1; while(n > 0) { change(a,1,n--); //去除最後一個元素,即前一個有序堆的最大元素 sink(a,1,n); } }
注意在while循環中,sink()方法多了一個參數,這個參數的目的是去掉上一個有序堆的最大元素。
所有代碼以下:
public class HeapSort extends SortBase { /* (non-Javadoc) * @see Sort.SortBase#sort(java.lang.Integer[]) */ @Override public Integer[] sort(Integer[] a) { // TODO Auto-generated method stub print("init",a); heapSort(a); print("result",a); return null; } public void buildBinaryHeapWithSink(Integer[] a) { //index based on 1 for(int k=a.length/2;k>=1;k--) { sink(a,k); } } public void buildBinaryHeapWithSwim(Integer[] a) { for(int k=2;k<a.length;k++) { swim(a,k); } } public void heapSort(Integer[] a) { for(int k=a.length/2;k>=1;k--) { sink(a,k); } Integer n = a.length - 1; while(n > 0) { change(a,1,n--); //去除最後一個元素,即前一個有序堆的最大元素 sink(a,1,n); } } //index based on 1 public void swim(Integer[] a,Integer key) { while(key > 1 && a[key/2] < a[key]) { change(a,key/2,key); key /= 2; } } //index based on 1 public void sink(Integer[] a,Integer key) { Integer max = key*2; while(key*2 < a.length - 1) { if(a[key*2] < a[key*2 + 1]) { max = key*2 + 1; } else { max = key*2; } if(a[key] > a[max]) break; change(a,key,max); key = max; } } public void sink(Integer[] a,Integer key,Integer n) { Integer max = key*2; while(key*2 < n) { if(a[key*2] < a[key*2 + 1]) { max = key*2 + 1; } else { max = key*2; } if(a[key] > a[max]) break; change(a,key,max); key = max; } } public static void main(String[] args) { Integer[] a = {null,2,1,5,9,0,6,8,7,3}; //(new HeapSort()).sort(a); (new HeapSort()).buildBinaryHeapWithSink(a); print("a",a); } }
堆排序的平均時間複雜度爲NlogN