跳轉至
紙質書（簡體中文版）已發行，詳情請見這裡

# 11.2 選擇排序

選擇排序（selection sort）的工作原理非常簡單：開啟一個迴圈，每輪從未排序區間選擇最小的元素，將其放到已排序區間的末尾。

設陣列的長度為 \(n\) ，選擇排序的演算法流程如圖 11-2 所示。

1. 初始狀態下，所有元素未排序，即未排序（索引）區間為 \([0, n-1]\) 。
2. 選取區間 \([0, n-1]\) 中的最小元素，將其與索引 \(0\) 處的元素交換。完成後，陣列前 1 個元素已排序。
3. 選取區間 \([1, n-1]\) 中的最小元素，將其與索引 \(1\) 處的元素交換。完成後，陣列前 2 個元素已排序。
4. 以此類推。經過 \(n - 1\) 輪選擇與交換後，陣列前 \(n - 1\) 個元素已排序。
5. 僅剩的一個元素必定是最大元素，無須排序，因此陣列排序完成。

<1><2><3><4><5><6><7><8><9><10><11>

圖 11-2 選擇排序步驟

在程式碼中，我們用 \(k\) 來記錄未排序區間內的最小元素：

Python C++Java C#Go Swift JS TS Dart Rust C Kotlin Ruby Zig
selection_sort.py

    def selection_sort(nums: list[int]):
        """選擇排序"""
        n = len(nums)
        # 外迴圈：未排序區間為 [i, n-1]
        for i in range(n - 1):
            # 內迴圈：找到未排序區間內的最小元素
            k = i
            for j in range(i + 1, n):
                if nums[j] < nums[k]:
                    k = j  # 記錄最小元素的索引
            # 將該最小元素與未排序區間的首個元素交換
            nums[i], nums[k] = nums[k], nums[i]

selection_sort.cpp

    /* 選擇排序 */
    void selectionSort(vector<int> &nums) {
        int n = nums.size();
        // 外迴圈：未排序區間為 [i, n-1]
        for (int i = 0; i < n - 1; i++) {
            // 內迴圈：找到未排序區間內的最小元素
            int k = i;
            for (int j = i + 1; j < n; j++) {
                if (nums[j] < nums[k])
                    k = j; // 記錄最小元素的索引
            }
            // 將該最小元素與未排序區間的首個元素交換
            swap(nums[i], nums[k]);
        }
    }

selection_sort.java

    /* 選擇排序 */
    void selectionSort(int[] nums) {
        int n = nums.length;
        // 外迴圈：未排序區間為 [i, n-1]
        for (int i = 0; i < n - 1; i++) {
            // 內迴圈：找到未排序區間內的最小元素
            int k = i;
            for (int j = i + 1; j < n; j++) {
                if (nums[j] < nums[k])
                    k = j; // 記錄最小元素的索引
            }
            // 將該最小元素與未排序區間的首個元素交換
            int temp = nums[i];
            nums[i] = nums[k];
            nums[k] = temp;
        }
    }

selection_sort.cs

    /* 選擇排序 */
    void SelectionSort(int[] nums) {
        int n = nums.Length;
        // 外迴圈：未排序區間為 [i, n-1]
        for (int i = 0; i < n - 1; i++) {
            // 內迴圈：找到未排序區間內的最小元素
            int k = i;
            for (int j = i + 1; j < n; j++) {
                if (nums[j] < nums[k])
                    k = j; // 記錄最小元素的索引
            }
            // 將該最小元素與未排序區間的首個元素交換
            (nums[k], nums[i]) = (nums[i], nums[k]);
        }
    }

selection_sort.go

    /* 選擇排序 */
    func selectionSort(nums []int) {
        n := len(nums)
        // 外迴圈：未排序區間為 [i, n-1]
        for i := 0; i < n-1; i++ {
            // 內迴圈：找到未排序區間內的最小元素
            k := i
            for j := i + 1; j < n; j++ {
                if nums[j] < nums[k] {
                    // 記錄最小元素的索引
                    k = j
                }
            }
            // 將該最小元素與未排序區間的首個元素交換
            nums[i], nums[k] = nums[k], nums[i]

        }
    }

selection_sort.swift

    /* 選擇排序 */
    func selectionSort(nums: inout [Int]) {
        // 外迴圈：未排序區間為 [i, n-1]
        for i in nums.indices.dropLast() {
            // 內迴圈：找到未排序區間內的最小元素
            var k = i
            for j in nums.indices.dropFirst(i + 1) {
                if nums[j] < nums[k] {
                    k = j // 記錄最小元素的索引
                }
            }
            // 將該最小元素與未排序區間的首個元素交換
            nums.swapAt(i, k)
        }
    }

selection_sort.js

    /* 選擇排序 */
    function selectionSort(nums) {
        let n = nums.length;
        // 外迴圈：未排序區間為 [i, n-1]
        for (let i = 0; i < n - 1; i++) {
            // 內迴圈：找到未排序區間內的最小元素
            let k = i;
            for (let j = i + 1; j < n; j++) {
                if (nums[j] < nums[k]) {
                    k = j; // 記錄最小元素的索引
                }
            }
            // 將該最小元素與未排序區間的首個元素交換
            [nums[i], nums[k]] = [nums[k], nums[i]];
        }
    }

selection_sort.ts

    /* 選擇排序 */
    function selectionSort(nums: number[]): void {
        let n = nums.length;
        // 外迴圈：未排序區間為 [i, n-1]
        for (let i = 0; i < n - 1; i++) {
            // 內迴圈：找到未排序區間內的最小元素
            let k = i;
            for (let j = i + 1; j < n; j++) {
                if (nums[j] < nums[k]) {
                    k = j; // 記錄最小元素的索引
                }
            }
            // 將該最小元素與未排序區間的首個元素交換
            [nums[i], nums[k]] = [nums[k], nums[i]];
        }
    }

selection_sort.dart

    /* 選擇排序 */
    void selectionSort(List<int> nums) {
      int n = nums.length;
      // 外迴圈：未排序區間為 [i, n-1]
      for (int i = 0; i < n - 1; i++) {
        // 內迴圈：找到未排序區間內的最小元素
        int k = i;
        for (int j = i + 1; j < n; j++) {
          if (nums[j] < nums[k]) k = j; // 記錄最小元素的索引
        }
        // 將該最小元素與未排序區間的首個元素交換
        int temp = nums[i];
        nums[i] = nums[k];
        nums[k] = temp;
      }
    }

selection_sort.rs

    /* 選擇排序 */
    fn selection_sort(nums: &mut [i32]) {
        if nums.is_empty() {
            return;
        }
        let n = nums.len();
        // 外迴圈：未排序區間為 [i, n-1]
        for i in 0..n - 1 {
            // 內迴圈：找到未排序區間內的最小元素
            let mut k = i;
            for j in i + 1..n {
                if nums[j] < nums[k] {
                    k = j; // 記錄最小元素的索引
                }
            }
            // 將該最小元素與未排序區間的首個元素交換
            nums.swap(i, k);
        }
    }

selection_sort.c

    /* 選擇排序 */
    void selectionSort(int nums[], int n) {
        // 外迴圈：未排序區間為 [i, n-1]
        for (int i = 0; i < n - 1; i++) {
            // 內迴圈：找到未排序區間內的最小元素
            int k = i;
            for (int j = i + 1; j < n; j++) {
                if (nums[j] < nums[k])
                    k = j; // 記錄最小元素的索引
            }
            // 將該最小元素與未排序區間的首個元素交換
            int temp = nums[i];
            nums[i] = nums[k];
            nums[k] = temp;
        }
    }

selection_sort.kt

    /* 選擇排序 */
    fun selectionSort(nums: IntArray) {
        val n = nums.size
        // 外迴圈：未排序區間為 [i, n-1]
        for (i in 0..<n - 1) {
            var k = i
            // 內迴圈：找到未排序區間內的最小元素
            for (j in i + 1..<n) {
                if (nums[j] < nums[k])
                    k = j // 記錄最小元素的索引
            }
            // 將該最小元素與未排序區間的首個元素交換
            val temp = nums[i]
            nums[i] = nums[k]
            nums[k] = temp
        }
    }

selection_sort.rb

    ### 選擇排序 ###
    def selection_sort(nums)
      n = nums.length
      # 外迴圈：未排序區間為 [i, n-1]
      for i in 0...(n - 1)
        # 內迴圈：找到未排序區間內的最小元素
        k = i
        for j in (i + 1)...n
          if nums[j] < nums[k]
            k = j # 記錄最小元素的索引
          end
        end
        # 將該最小元素與未排序區間的首個元素交換
        nums[i], nums[k] = nums[k], nums[i]
      end
    end

selection_sort.zig

    [class]{}-[func]{selectionSort}

視覺化執行

全螢幕觀看 >

## 11.2.1 演算法特性

- 時間複雜度為 \(O(n^2)\)、非自適應排序：外迴圈共 \(n - 1\) 輪，第一輪的未排序區間長度為 \(n\) ，最後一輪的未排序區間長度為 \(2\) ，即各輪外迴圈分別包含 \(n\)、\(n - 1\)、\(\dots\)、\(3\)、\(2\) 輪內迴圈，求和為 \(\frac{(n - 1)(n + 2)}{2}\) 。
- 空間複雜度為 \(O(1)\)、原地排序：指標 \(i\) 和 \(j\) 使用常數大小的額外空間。
- 非穩定排序：如圖 11-3 所示，元素 `nums[i]` 有可能被交換至與其相等的元素的右邊，導致兩者的相對順序發生改變。

圖 11-3 選擇排序非穩定示例

#####