堆排序(Heap Sort)
堆排序(Heapsort)是指利用堆這種資料結構所設計的一種排序演算法。堆積是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。
什麼是堆?
堆其實是一種特殊的樹。只要滿足這兩點,它就是一個堆。
- 堆是一個完全二叉樹(除了最後一層,其他層的節點個數都是滿的,最後一層的節點都靠左排列)。
- 堆中每一個節點的值都必須大於等於(或小於等於)其子樹中每個節點的值。
對於每個節點的值都大於等於子樹中每個節點值的堆,我們叫作大頂堆。(正序排序)
對於每個節點的值都小於等於子樹中每個節點值的堆,我們叫作小頂堆。(逆序排序)
其中圖 1 和 圖 2 是大頂堆,圖 3 是小頂堆,圖 4 不是堆。除此之外,從圖中還可以看出來,對於同一組資料,我們可以構建多種不同形態的堆。
演算法描述
- 將待排序序列構建成一個大頂堆,此堆為初始的無序區(其實此時arr[0]已經是最大值,如上圖1圖2,需要繼續對子節點進行排序)
- 將堆頂與最後一個元素交換,得到有序序列和無序序列,無序序列的所有元素小於有序序列最小的元素
- 交換後堆被破壞,重新構建大頂堆,重複2步驟arr.length - 1 次,此時排序完成;
動圖演示
表示一個圖壓根看不懂(其實兩個也看不懂)
程式碼實現
let arr = [3, 45, 16, 8, 65, 15, 36, 22, 19, 1, 96, 12, 56, 12, 45];
let len = arr.length;
let heapSort = arr => {
// 構建大頂堆
for (let i = Math.trunc(len / 2 - 1); i >= 0; i--) {
heapify(arr, i, len);
}
// 構建完成之後,對剩餘元素繼續構建大頂堆
for (let i = Math.floor(arr.length - 1); i > 0; i--) {
// 將根元素(最小或最大元素)與最後一個元素交換
swap(arr, 0, i);
// 繼續進行構建大頂堆
heapify(arr, 0, i);
}
console.log(arr);
};
let swap = (arr, i, j) => {
let temp;
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
};
// 將 i 結點以下的堆整理為大頂堆,注意這一步實現的基礎實際上是:
// 假設結點 i 以下的子堆已經是一個大頂堆,heapify 函式實現的
// 功能是實際上是:找到 結點 i 在包括結點 i 的堆中的正確位置。
// 後面將寫一個 for 迴圈,從第一個非葉子結點開始,對每一個非葉子結點
// 都執行 heapify 操作,所以就滿足了結點 i 以下的子堆已經是一大頂堆
let heapify = (arr, i, length) => {
let temp = arr[i];
for (let j = 2 * i + 1; j < length; j = 2 * j + 1) {
if (j + 1 < length && arr[j] > arr[j + 1]) {
j++;
}
if (temp > arr[j]) {
swap(arr, i, j);
i = j;
} else {
break;
}
}
};
heapSort(arr);
複製程式碼
結果:
堆排序包括建堆和排序兩個操作,建堆過程的時間複雜度是 O(n),排序過程的時間複雜度是 O(nlogn),所以,堆排序整體的時間複雜度是 O(nlogn)。 最佳情況:T(n) = O(nlogn)。 最差情況:T(n) = O(nlogn)。 平均情況:T(n) = O(nlogn)。
另外從堆的行政來看,堆排序是不穩定的排序。
總結
堆排序也是一種選擇排序,從名字上乍一看很高深的感覺,但是結合堆的特性來看其實比其他排序更簡單(至少比希爾排序簡單hhh),重點在於理解什麼是堆,以及大頂堆和小頂堆
參考
下期預告
【小小前端】前端排序演算法第四期(另一種交換排序之快速排序)