參考文章: 前言
堆排序、快速排序、歸併排序的平均時間複雜度都為o(n*logn)
。要弄清楚堆排序,就要先了解下二叉堆
這種資料結構。本文不打算完全講述二叉堆的所有操作,而是著重講述堆排序中要用到的操作。比如我們建堆的時候可以採用堆的插入操作(將元素插入到適當的位置,使新的序列仍符合堆的定義)將元素乙個乙個地插入到堆中,但其實我們完全沒必要這麼做,我們有執行操作更少的方法,後面你會看到,我們基本上只用到了堆的刪除操作,更具體地說,應該是刪除堆的根節點後,將剩餘元素繼續調整為堆的操作。先來看二叉堆的定義。
二叉堆二叉堆其實是一棵有著特殊性質的完全二叉樹
,這裡的特殊性質是指:
如果乙個二叉堆的父節點的值總是大於其左右孩子的值,那麼該二叉堆為最大堆,反之為最小堆。我們在排序時,如果要排序後的順序為從小到大,則需選擇最大堆,反之,選擇最小堆
,這點通過後面對堆排序分析,你會有所體會。
堆排序
由二叉堆的定義可知,堆頂元素(即二叉堆的根節點)
一定為堆中的最大值或最小值,因此如果我們輸出堆頂元素後,將剩餘的元素再調整為二叉堆,繼而再次輸出堆頂元素,再將剩餘的元素調整為二叉堆,反覆執行該過程,這樣便可輸出乙個有序序列,這個過程我們就叫做堆排序。
由於我們的輸入是乙個無序序列,因此要實現堆排序,我們要先後解決如下兩個問題:
1、如何將乙個無序序列建成乙個二叉堆;
2、在去掉堆頂元素後,如何將剩餘的元素調整為乙個二叉堆。
針對第乙個問題,可能很明顯會想到用堆的插入操作,乙個乙個地插入元素,每次插入後調整元素的位置,使新的序列依然為二叉堆。這種操作一般是自底向上的調整操作,即先將待插入元素放在二叉堆後面,而後逐漸向上將其與父節點比較,進而調整位置。但正如前言中所說,我們完全用不著乙個節點乙個節點地插入,那我們要怎麼做呢?我們需要先來解決第二個問題,解決了第二個問題,第乙個問題問題也就迎刃而解了。
調整二叉堆
要分析第二個問題,我們先給出以下前提:
1、我們排序的目標是從小到大,因此我們用最大堆;
2、我們將二叉堆中的元素以層序遍歷後的順序儲存在一維陣列中,根節點在陣列中的位置序號為0。
這樣,如果某個節點在陣列中的位置序號為i
,那麼它的左右孩子的位置序號分別為2i+1
和2i+2
。
為了使調整過程更易於理解,我們採用如下二叉堆來分析(注意下面的分析,我們並沒有採用額外的陣列來儲存每次去掉的堆頂資料):
這裡陣列a
中元素的個數為8
,很明顯最大值為a0
,為了實現排序後的元素按照從小到大的順序排列,我們可以將二叉堆中的最後乙個元素a7
與a0
互換,這樣a7
中儲存的就是陣列中的最大值,而此時該二叉樹變為了如下情況:
為了將其調整為二叉堆,我們需要尋找4
應該插入的位置。為此,我們讓4與它的孩子節點中最大的那個,也就是其左孩子7
,進行比較,由於4<7,
我們便把二者互換,這樣二叉樹便變成了如下的形式:
為了將其調整為二叉堆,一樣將3
與其孩子結點中的最大值比較,由於3<6
,需要將二者互換,而後繼續和其孩子節點比較,需要將3和4互換
,最終再次調整好的二叉堆形式如下:
一樣將a0
與此時堆中的最後乙個元素a5
互換,這樣a5
中儲存的便是第三大的數值,再次調整剩餘的節點,如此反覆,直到最後堆中僅剩乙個元素,這時整個陣列便已經按照從小到大的順序排列好了。
據此,我們不難得出將剩餘元素繼續調整為二叉堆的操作實現**如下(同前面一樣,我們不需每次比較後都交換元素位置,**中可以再次體會到這點):
這樣,將已經建好的二叉堆進行排序的**如下:/*arr[start+1...end]滿足最大堆的定義,
將arr[start]加入到最大堆arr[start+1...end]中,
調整arr[start]的位置,使arr[start...end]也成為最大堆
注:由於陣列從0開始計算序號,也就是二叉堆的根節點序號為0,因此序號為i的左右子節點的序號分別為2i+1和2i+2
*/public
void
maxheapfixdown(int arr,int start,int end)
arr[start] = temp;
}
建立二叉堆public
void
maxheapsort(int arr,int len)
}
搞懂了第二個問題,那麼我們回過頭來看如何將無序的陣列建成乙個二叉堆。
我們同樣以上面的陣列為例,假設其陣列內元素的原始順序為:a=
,那麼在沒有建成二叉堆前,個元素在該完全二叉樹中的存放位置如下:
這裡的後面四個元素均為葉子節點,很明顯,這四個葉子可以認為是乙個堆(因為堆的定義中並沒有對左右孩子間的關係有任何要求,所以可以將這幾個葉子節點看做是乙個堆),而後我們便考慮將第乙個非葉子節點9
插入到這個堆中,再次構成乙個堆,接著再將3
插入到新的堆中,再次構成新堆,如此繼續,直到該二叉樹的根節點6也插入到了該堆中,此時構成的堆便是由該陣列建成的二叉堆。因此,我們這裡同樣可以利用到上面所寫的maxheapfixdown(int arr,int start,int end)
函式,因此建堆的**可寫成如下的形式:
測試**//把陣列建成大根堆
//第乙個非葉子節點的位置序號為(len-1)/2
public
void
makemaxheap(int arr,int len)
}
結果如下:@test
public
void
test() ;
int len = arr.length;
//堆化陣列
makemaxheap(arr,len);
//堆排序
maxheapsort(arr,len);
system.out.println(arrays.tostring(arr));
}
時間複雜度
最後我們簡要分析下堆排序的時間複雜度。我們在每次重新調整堆時,都要將父節點與孩子節點比較,這樣,每次重新調整堆的時間複雜度變為o(logn)
,而堆排序時有n-1
次重新調整堆的操作,建堆時有((len-1)/2+1)
次重新調整堆的操作,因此堆排序的平均時間複雜度為o(n*logn)
。由於我們這裡沒有借用輔助儲存空間,因此空間複雜度為o(1)
。
堆排序在排序元素較少時有點大才小用,待排序列元素較多時,堆排序還是很有效的。另外,堆排序在最壞情況下,時間複雜度也為o(n*logn)
。相對於快速排序(平均時間複雜度為o(n*logn)
,最壞情況下為o(n*n)
),這是堆排序的最大優點。
排序演算法7 堆排序
0.引用 大頂堆 用來排公升序 1.什麼是堆排序 堆排序 heapsort 是指利用堆這種資料結構所設計的一種排序演算法。堆積是乙個近似完全二叉樹的結構,並同時滿足堆積的性質 即子結點 的鍵值或索引總是小於 或者大於 它的父節點。堆排序可以說是一種利用堆的概念來排序的選擇排序。分為兩種方法 大頂堆 ...
排序演算法 (7)堆排序
堆排序是利用堆這種資料結構而設計的一種排序演算法,堆排序是一種選擇排序,它的最壞,最好,平均時間複雜度均為o nlogn 它也是不穩定排序。首先簡單了解下堆結構。堆是具有以下性質的完全二叉樹 每個結點的值都大於或等於其左右孩子結點的值,稱為大頂堆 或者每個結點的值都小於或等於其左右孩子結點的值,稱為...
排序演算法之(7) 堆排序
堆排序主要是利用了堆的性質,對於大頂堆 堆中的每個節點的值都不小於它的孩子節點的值,詳細可參考我的另一篇部落格那麼大頂堆的堆頂元素就是當前堆中所有元素中最大的。利用這個性質,進行如下操作,則可以得到乙個有序序列 將待排序的n個元素乙個乙個插入堆中,那麼此時堆頂元素就是所有元素中最大的 將堆頂元素取出...