1. 程式人生 > 其它 >詳解排序演算法(三)之2種選擇排序(簡單選擇排序、堆排序)

詳解排序演算法(三)之2種選擇排序(簡單選擇排序、堆排序)

簡單選擇排序

演算法步驟

  1. 遍歷數列,找到最小的值,置於第1位
  2. 從第2位開始,遍歷數列,找到最小的值,置於第2位
  3. 以此類推,直到開始位置變為最後一位,排序結束。

示例

我們取 3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48 來進行示範

  1. 第1輪排序,從第1位 3 開始,遍歷數列,找到最小值 2,將其置於第1位,即與 3 交換位置,得 2, 44, 38, 5, 47, 15, 36, 26, 27, 3, 46, 4, 19, 50, 48

  2. 第2輪排序,從第2位 44 開始,遍歷數列,找到最小值 3,將其置於第2位,即與 44

    交換位置,得 2, 3, 38, 5, 47, 15, 36, 26, 27, 44, 46, 4, 19, 50, 48

  3. 第3輪排序,從第3位 38 開始,遍歷數列,找到最小值 4,將其置於第3位,即與 38 交換位置,得 2, 3, 4, 5, 47, 15, 36, 26, 27, 44, 46, 38, 19, 50, 48

  4. 以此類推,直到開始位置變為最後一位,排序結束。

動態圖

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
}

堆排序

在講堆排序之前,我們先了解一些二叉樹的知識。

  1. 二叉樹

    簡單地理解,滿足以下兩個條件的樹就是二叉樹:

    • 本身是有序樹;

    • 樹中包含的各個節點的度不能超過 2,即只能是 0、1 或者 2;

  2. 滿二叉樹

    如果二叉樹中除了葉子結點,每個結點的度都為 2,則此二叉樹稱為滿二叉樹。

  3. 完全二叉樹

    如果二叉樹中除去最後一層節點為滿二叉樹,且最後一層的結點依次從左到右分佈,則此二叉樹被稱為完全二叉樹。

​ 對於完全二叉樹來說,如果記根節點的索引值為0,那麼對於任意一個節點i,其左右節點如果存在的話,索引值分別為 2i + 1,2i + 2。之所以介紹二叉樹,是因為堆排序就要用到我們上面所講的完全二叉樹。

演算法步驟

  1. 建立大跟堆或小跟堆,我們以大根堆為例,所謂建立大跟堆,就是各個節點與其左右節點比較,將大的值置換到根部,這樣一個個節點比較下去,直到將最大的值置換到根節點,大跟堆建立完畢。

  2. 將大根堆中的根節點與其尾節點進行交換

  3. 將二叉樹的長度減一,即尾結點不參與下一輪排序。重複操作1,直到直到堆的尺寸為 1。

示例

我們取 91, 60, 96, 13, 35, 65, 46, 65, 10, 30, 20, 31, 77, 81, 22 進行示範。

數列對應的完全二叉樹如下圖

第一輪排序

  1. 4681、22 比較,81最大,81 與 46 互換位置,得

  2. 6531、77 比較,77最大,77 與 65 互換位置,得

  3. 3530、20 比較,35最大,位置不變。

  4. 1365、10 比較,65最大,65 與 13 互換,得

  5. 9677、81 比較,96最大,位置不變

  6. 6065、35比較,65最大,65 與 60 互換,得

  7. 9165、96比較,96最大,96 與 91互換,得到最大值96

  8. 將根節點96與尾結點22互換,即最大值置於尾結點。數列長度減一,即尾節點不再參與堆構建,如圖

  9. 第一輪排序結束,重複以上步驟,直到堆的尺寸為 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) 不穩定