詳解排序演算法(三)之2種選擇排序(簡單選擇排序、堆排序)
簡單選擇排序
演算法步驟
- 遍歷數列,找到最小的值,置於第1位
- 從第2位開始,遍歷數列,找到最小的值,置於第2位
- 以此類推,直到開始位置變為最後一位,排序結束。
示例
我們取 3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48 來進行示範
-
第1輪排序,從第1位 3 開始,遍歷數列,找到最小值 2,將其置於第1位,即與 3 交換位置,得 2, 44, 38, 5, 47, 15, 36, 26, 27, 3, 46, 4, 19, 50, 48
-
第2輪排序,從第2位 44 開始,遍歷數列,找到最小值 3,將其置於第2位,即與 44
-
第3輪排序,從第3位 38 開始,遍歷數列,找到最小值 4,將其置於第3位,即與 38 交換位置,得 2, 3, 4, 5, 47, 15, 36, 26, 27, 44, 46, 38, 19, 50, 48
-
以此類推,直到開始位置變為最後一位,排序結束。
動態圖
javascript程式碼
function selectionSort (arr) { let min, temp for (let i = 0; i < arr.length - 1; i++) { min = i // 最小數索引 for (let j = i; j < arr.length; j++) { if (arr[j] < arr[min]) { min = j } } temp = arr[i] arr[i] = arr[min] arr[min] = temp } return arr }
堆排序
在講堆排序之前,我們先了解一些二叉樹的知識。
-
二叉樹
簡單地理解,滿足以下兩個條件的樹就是二叉樹:
-
本身是有序樹;
-
樹中包含的各個節點的度不能超過 2,即只能是 0、1 或者 2;
-
-
滿二叉樹
如果二叉樹中除了葉子結點,每個結點的度都為 2,則此二叉樹稱為滿二叉樹。
-
完全二叉樹
如果二叉樹中除去最後一層節點為滿二叉樹,且最後一層的結點依次從左到右分佈,則此二叉樹被稱為完全二叉樹。
對於完全二叉樹來說,如果記根節點的索引值為0,那麼對於任意一個節點i,其左右節點如果存在的話,索引值分別為 2i + 1,2i + 2。之所以介紹二叉樹,是因為堆排序就要用到我們上面所講的完全二叉樹。
演算法步驟
-
建立大跟堆或小跟堆,我們以大根堆為例,所謂建立大跟堆,就是各個節點與其左右節點比較,將大的值置換到根部,這樣一個個節點比較下去,直到將最大的值置換到根節點,大跟堆建立完畢。
-
將大根堆中的根節點與其尾節點進行交換
-
將二叉樹的長度減一,即尾結點不參與下一輪排序。重複操作1,直到直到堆的尺寸為 1。
示例
我們取 91, 60, 96, 13, 35, 65, 46, 65, 10, 30, 20, 31, 77, 81, 22 進行示範。
數列對應的完全二叉樹如下圖
第一輪排序
-
46 與 81、22 比較,81最大,81 與 46 互換位置,得
-
65 與 31、77 比較,77最大,77 與 65 互換位置,得
-
35 與 30、20 比較,35最大,位置不變。
-
13 與 65、10 比較,65最大,65 與 13 互換,得
-
96 與 77、81 比較,96最大,位置不變
-
60 與 65、35比較,65最大,65 與 60 互換,得
-
91 與 65、96比較,96最大,96 與 91互換,得到最大值96
-
將根節點96與尾結點22互換,即最大值置於尾結點。數列長度減一,即尾節點不再參與堆構建,如圖
-
第一輪排序結束,重複以上步驟,直到堆的尺寸為 1,排序結束。
動態圖
javascript程式碼
let last // 參與堆構建的最後一個節點索引值
// 建立大根堆
function buildMaxHeap(arr) {
for (let i = Math.floor((last + 1) / 2); i >= 0; i--) {
heapify(arr, i)
}
}
// 堆調整函式,此處為構建大根堆
function heapify (arr, i) {
// 在完全二叉樹中,索引值從0開始,若節點i存在左右子節點,則左右子節點索引分別為 2i + 1, 2i + 2
let left = 2 * i + 1,
right = 2 * i + 2,
largest = i
if (left <= last && arr[left] > arr[largest]) {
largest = left
}
if (right <= last && arr[right] > arr[largest]) {
largest = right
}
if (largest != i) {
swap(arr, i, largest)
}
}
function swap(arr, i, j) {
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
// 堆排序
function heapSort(arr) {
for (let i = 0; i < arr.length - 1; i++) {
last = arr.length - 1 - i
buildMaxHeap(arr)
swap(arr, 0, last)
}
return arr
}
兩種演算法的複雜度及穩定性
排序演算法 | 時間複雜度(平均) | 時間複雜度(最壞) | 時間複雜度(最好) | 空間複雜度 | 穩定性 |
---|---|---|---|---|---|
簡單選擇排序 | O(n2) | O(n2) | O(n2) | O(1) | 穩定 |
堆排序 | O(nlog2n) | O(nlog2n) | O(nlog2n) | O(log2n) | 不穩定 |