选择排序
最后更新
选择排序把数组分成左边的有序区和右边的无序区。每一轮扫描无序区,找出最小的元素,再把它交换到无序区的第一个位置,有序区就增加一个元素。点击上方的“播放”,观看扫描和交换的过程,也可以一次只看一次比较。
无论输入是什么,选择排序的比较次数都相同,但它最多只做 n-1 次交换,比冒泡排序少得多。在写入代价很高时,这一点可能很重要。
时间复杂度与空间复杂度
| 情况 | 复杂度 | 说明 |
|---|---|---|
| 最好情况 | O(n²) | 即使已经有序也要比较 |
| 平均情况 | O(n²) | 随机顺序 |
| 最坏情况 | O(n²) | 逆序排列 |
| 空间 | O(1) | 原地排序 |
| 稳定性 | 不稳定 | 交换可能打乱相等元素的顺序 |
分步说明
| 步骤 | 发生了什么 |
|---|---|
| 1 | 把整个数组看作无序区。 |
| 2 | 扫描无序区,找出最小的元素。 |
| 3 | 把这个最小值交换到无序区的第一个位置。 |
| 4 | 把分界线向右移动一位(这个位置已经排好)。 |
| 5 | 重复以上步骤,直到无序区只剩一个元素。 |
实例演示
对 [5, 2, 4, 1] 排序:
| 轮次 | 数组 | 操作 |
|---|---|---|
| 开始 | [5, 2, 4, 1] | 整个数组都是无序的。 |
| 1 | [1, 2, 4, 5] | 扫描 [5, 2, 4, 1],最小值是下标 3 处的 1;把它与下标 0 交换。 |
| 2 | [1, 2, 4, 5] | 扫描 [2, 4, 5],最小值 2 已经在下标 1 处;与自身交换。 |
| 3 | [1, 2, 4, 5] | 扫描 [4, 5],最小值 4 已经在下标 2 处;无需移动。 |
| 完成 | [1, 2, 4, 5] | 只剩下 5,它已经在正确的位置上。 |
什么时候用选择排序
| 适合使用 | 不适合使用 |
|---|---|
写入代价很高:它最多只做 n-1 次交换。 | 数组很大:O(n²) 次比较会成为主要开销。 |
| 你需要一种简单、容易实现的原地排序。 | 你需要稳定排序,保留相等键的原有顺序。 |
内存紧张:它只用 O(1) 的额外空间。 | 数据几乎有序:它不能像插入排序那样提前结束。 |
| 数据量很小,而且看重性能的可预测性。 | 看重吞吐量:快速排序等 O(n log n) 的排序算法要快得多。 |
Selection Sort 代码
简洁、可运行的 Selection Sort 实现,提供 Python, JavaScript, Java, C++, C, Pseudocode 版本。选择一种语言,复制代码,或在 Coddy 在线编辑器中直接打开。
Selection Sort 的 Python 代码
Python
1def selection_sort(a):2 n = len(a)3 for i in range(n - 1):4 # Find the smallest element in the unsorted tail5 min_idx = i6 for j in range(i + 1, n):7 if a[j] < a[min_idx]:8 min_idx = j9 a[i], a[min_idx] = a[min_idx], a[i]10 return a11
12
13nums = [64, 25, 12, 22, 11]14print("Before:", nums)15selection_sort(nums)16print("After: ", nums)Selection Sort 的 JavaScript 代码
JavaScript
1function selectionSort(a) {2 for (let i = 0; i < a.length - 1; i++) {3 let min = i;4 // Find the smallest element in the unsorted tail5 for (let j = i + 1; j < a.length; j++) {6 if (a[j] < a[min]) min = j;7 }8 if (min !== i) [a[i], a[min]] = [a[min], a[i]];9 }10 return a;11}12
13const data = [5, 2, 9, 1, 7, 3];14console.log("Before:", data);15console.log("Sorted:", selectionSort([...data]));Selection Sort 的 Java 代码
Java
1import java.util.Arrays;2
3public class Main {4 static void selectionSort(int[] arr) {5 for (int i = 0; i < arr.length - 1; i++) {6 int minIndex = i;7 // Find the smallest value in the unsorted part8 for (int j = i + 1; j < arr.length; j++) {9 if (arr[j] < arr[minIndex]) minIndex = j;10 }11 int tmp = arr[i];12 arr[i] = arr[minIndex];13 arr[minIndex] = tmp;14 }15 }16
17 public static void main(String[] args) {18 int[] arr = {29, 10, 14, 37, 13, 5};19 System.out.println("Before: " + Arrays.toString(arr));20 selectionSort(arr);21 System.out.println("After: " + Arrays.toString(arr));22 }23}Selection Sort 的 C++ 代码
C++
1#include <iostream>2#include <utility>3#include <vector>4
5void printVec(const std::vector<int>& a) {6 for (int x : a) std::cout << x << " ";7 std::cout << "\n";8}9
10void selectionSort(std::vector<int>& a) {11 for (size_t i = 0; i + 1 < a.size(); ++i) {12 // Find the smallest element in the unsorted suffix13 size_t minIdx = i;14 for (size_t j = i + 1; j < a.size(); ++j) {15 if (a[j] < a[minIdx]) minIdx = j;16 }17 std::swap(a[i], a[minIdx]);18 }19}20
21int main() {22 std::vector<int> data = {29, 10, 14, 37, 13, 5};23 std::cout << "Before: ";24 printVec(data);25 selectionSort(data);26 std::cout << "After: ";27 printVec(data);28 return 0;29}Selection Sort 的 C 代码
C
1#include <stdio.h>2
3void printArr(const int a[], int n) {4 for (int i = 0; i < n; i++) printf("%d ", a[i]);5 printf("\n");6}7
8void selectionSort(int a[], int n) {9 for (int i = 0; i < n - 1; i++) {10 // Find the smallest element in the unsorted suffix11 int minIdx = i;12 for (int j = i + 1; j < n; j++) {13 if (a[j] < a[minIdx]) minIdx = j;14 }15 int tmp = a[i];16 a[i] = a[minIdx];17 a[minIdx] = tmp;18 }19}20
21int main(void) {22 int data[] = {29, 10, 14, 37, 13, 5};23 int n = sizeof(data) / sizeof(data[0]);24 printf("Before: ");25 printArr(data, n);26 selectionSort(data, n);27 printf("After: ");28 printArr(data, n);29 return 0;30}Selection Sort 的 Pseudocode 代码
Pseudocode
1DECLARE nums : ARRAY[1:7] OF INTEGER2DECLARE n : INTEGER3n ← 74nums[1] ← 75nums[2] ← 36nums[3] ← 97nums[4] ← 18nums[5] ← 59nums[6] ← 810nums[7] ← 211DECLARE i : INTEGER12DECLARE j : INTEGER13DECLARE minIndex : INTEGER14DECLARE temp : INTEGER15
16// Select the smallest remaining value and swap it into place17FOR i ← 1 TO n - 118 minIndex ← i19 FOR j ← i + 1 TO n20 IF nums[j] < nums[minIndex] THEN21 minIndex ← j22 ENDIF23 NEXT j24 IF minIndex <> i THEN25 temp ← nums[i]26 nums[i] ← nums[minIndex]27 nums[minIndex] ← temp28 ENDIF29NEXT i30
31FOR i ← 1 TO n32 OUTPUT nums[i]33NEXT i选择排序常见问题
选择排序的时间复杂度是多少?
选择排序在所有情况下(最好、平均和最坏)都是
O(n²),因为每找一个最小值都要扫描整个无序区。它只用 O(1) 的额外空间。选择排序是稳定的吗?
标准的原地版本不稳定,因为把远处的最小值交换过来时,可能让一个相等的元素越过另一个。稳定的变体确实存在,但需要用移动代替交换。
选择排序什么时候有用?
当写入内存的代价很高时它很有用,因为它最多只做
n-1 次交换,这是移动元素的比较排序所能做到的最少次数。选择排序和冒泡排序有什么区别?
两者都是
O(n²) 的比较排序,但选择排序最多做 n-1 次交换,而冒泡排序最多可能做 O(n²) 次交换。冒泡排序还能发现数组已经有序并提前停止,而选择排序总是跑完全部轮次。该用选择排序还是插入排序?
大多数情况下优先用插入排序:它是稳定的,在接近有序的数据上只需
O(n),平均速度也更快。只有当减少写入次数是首要目标时才选选择排序,因为它保证最多只做 n-1 次交换。为什么选择排序在有序数组上也是 O(n²)?
选择排序不扫描无序区的其余部分,就无法知道某个元素是否已经是最小值,所以无论输入顺序如何,它每一轮都要做完所有比较。这意味着它的最好情况和最坏情况一样,都是
O(n²);而插入排序或冒泡排序则可以提前结束。