昨晚又重新看了看以前一直沒仔細研究的堆排序,記錄篇文章,把思路記錄一下。歡迎大家訪問我的部落格。(荒廢了好久的部落格又開張了,這次用的是 gitpage,靜態網站生成器用的是 hugo )
廢話不多說,進入正文
什麼是堆
通俗點講,堆(英語:Heap)是具有以下性質的完全二叉樹,任意節點小於(或大於)它的左右子節點,最小值(或大值)在根元素位置。我們稱最小值在根節點(堆頂)的叫做小根(頂)堆,最大值在根節點(堆頂)的叫做大根(頂)堆。
總結性質如下:
- 任意節點小於(或大於)它的所有後裔,最小元(或最大元)在堆的根上(堆序性)。
- 堆總是一棵完全樹。即除了最底層,其他層的節點都被元素填滿,且最底層儘可能地從左到右填入。
二叉樹性質
除了堆的定義外,我們還需要了解一些樹的基本性質,下面程式碼會用到這些。
我們假設一個陣列的第一個元素(索引為 0)位於根節點,那麼可以得出一下定義:
- 任意節點 i 的左節點索引是 : 2 * i + 1
- 任意節點 i 的右節點索引是 : 2 * i + 2
- 最後一個非葉子節點的索引是 : floor(length / 2) - 1
堆排序
有了上面這些基礎後,我們就可以開始介紹一下堆排序的整體思路了。
-
將待排序陣列
(R0,R2....Rn)
構建為一個大(小)根堆; -
將堆頂元素
R[0]
與最後一個元素R[n]
交換,此時得到一個新的無序區(R0,R2,......Rn-1)
和新的有序區(Rn)
,且滿足R[0,2...n-1] <= R[n]
; -
此時堆頂的元素
R[0]
有可能違反堆的性質,因此我們需要重新重複第一步的操作,將(R0,R2,......Rn-1)
重新構建為大(小)根堆; -
迴圈執行上面的過程,直到有序區的元素為
n
,則排序完成。
對於堆排序,最重要的兩個操作就是
構造初始堆
和調整堆
,其實構造初始堆也是調整堆的過程,只不過構造初始堆是對所有的非葉節點都進行調整,調整堆則只需要對堆頂元素R[0]
做向下調整。
程式碼實現
程式碼實現分為兩部分,第一部分我們介紹如果把無序的陣列調整為一個符合堆性質的陣列
-
對於第一次構建堆,我們選擇從第一個非葉子節點開始調整,一直迴圈到根節點為止
-
對於調整堆,我們只需要從根節點開始,做一次調整就夠了,因為除了根節點外,其他的節點是滿足根節點性質的
上面描述的有點抽象,最好可以畫一個樹,然後一個節點一個節點調整。最難理解的大概就是構建堆的過程了。
/**
* 構建堆
*
* @param [array] $arr [待排序無序陣列]
* @param [int] $start [第一個需要調整的非葉子節點]
* @param [int] $len [元素個數]
*
* @return void
*/
function heapAdjust(&$arr, $start, $len)
{
for ($child = $start * 2 + 1; $child < $len; $child = $child * 2 + 1) {
//左節點小於右節點
if ($child != $len - 1 && $arr[$child] < $arr[$child + 1]) {
$child++; //此時子節點指向右子節點
}
//滿足大頂(根)堆
if ($arr[$start] >= $arr[$child]) {
break;
}
//和子節點進行交換
list($arr[$start], $arr[$child]) = array($arr[$child], $arr[$start]);
$start = $child;
}
}
第二步,我們來看看具體排序實現
/**
* [heapSort 堆排序實現]
*
* @param [array] $arr [待排序的無序陣列]
*
* @return void
*/
function heapSort(&$arr)
{
/**
* 將待排序陣列構建成一個大頂(根)堆
* 構建說明:從最後(下)一個非葉子節點,比較當前節點和子節點,找到最小的點,進行交換,
* 迴圈向堆頂遞進,最後就形成了一個大頂(根)堆
*/
$len = count($arr);
//第一次構建堆,我們從第一個非葉子節點開始調整一直迴圈到根節點為止,則構造完成
for ($i = ($len >> 1) - 1; $i >= 0; $i--) {
heapAdjust($arr, $i, $len);
}
for ($i = $len - 1; $i >= 0; $i--) {
//交換根頂和根尾元素
list($arr[0], $arr[$i]) = array($arr[$i], $arr[0]);
//調整堆,我們只需要從根節點開始向下調整
heapAdjust($arr, 0, $i);
}
}
時間複雜度與空間複雜度
堆執行一次調整需要O(logn)的時間,在排序過程中需要遍歷所有元素執行堆調整,所以最終時間複雜度是O(nlogn)。空間複雜度是O(1)。
總結
對堆排序理解的難點,大部分在於構建堆的過程的理解上,為何從第一個非葉子節點開始和子節點進行交換,一直迴圈到根頂,這個構建過程還需要親手畫畫圖,理解的會比較深。
本作品採用《CC 協議》,轉載必須註明作者和本文連結