1. 原理解析
假设你面前有一筐大小不一的苹果,你想把它们按从小到大排成一排。 你会怎么做?
- 你肯定是一眼扫过去,挑出一个最小的,放在第一位。
- 然后从剩下的苹果里,再挑出一个最小的,放在第二位。
- 以此类推,直到挑完所有的苹果。
这就是选择排序的灵魂:每次在未排序的序列中,选出最小(或最大)的那个元素,放到已排序序列的末尾。
2. 使用场景
- 数据量极小:对于几条、几十条数据,写起来快,逻辑简单,不用引入复杂的算法。
- 写入成本极高的设备:在所有 O(N²) 的排序算法中,选择排序有一个独特的优势——它的交换次数最少(最多只有 N-1 次交换)。如果某个硬件设备往内存里写数据的代价非常昂贵,选择排序比冒泡排序和插入排序都要好得多。
3. 代码实战
Java 版本
public class SelectionSort {
public static void sort(int[] arr) {
if (arr == null || arr.length < 2) return;
int n = arr.length;
// i 代表当前正在确定(选出最小值)的位置
for (int i = 0; i < n - 1; i++) {
// 假设当前 i 位置就是剩下的元素中最小的
int minIndex = i;
// 从 i 的下一个位置开始,去剩下的元素里找有没有更小的
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j; // 如果发现更小的,记录它的下标!
}
}
// 找了一圈,如果真正的最小值不在初始假设的 i 位置,就把它交换过来
if (minIndex != i) {
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
}
}
Python 版本
def selection_sort(arr):
if not arr or len(arr) < 2:
return
n = len(arr)
# i 代表当前正在确定(选出最小值)的位置
for i in range(n - 1):
# 假设当前 i 位置就是剩下的元素中最小的
min_index = i
# 从 i+1 开始遍历剩下的元素找更小的
for j in range(i + 1, n):
if arr[j] < arr[min_index]:
min_index = j # 发现更小的,记录下标
# 找了一圈,如果最小值不是最初假设的 i,就交换
if min_index != i:
arr[i], arr[min_index] = arr[min_index], arr[i]
4. 核心难点与易错点详解
- 找最小值的范围与边界:外层循环变量设为
i,代表当前要确定的是第i个位置上的元素。那么内层寻找最小值的循环变量j,必须从i + 1开始找,因为前i个元素(也就是你之前已经挑出来的更小的苹果)已经排好序了。 - 手里拿着的是“编号”,而不是“实体苹果”:在挑最小苹果的过程中,我们用一个变量
minIndex记住当前最小苹果所在的位置(下标),而不是把苹果的值拿在手里。等剩下的一筐苹果都看了一遍后,再把minIndex位置上的苹果和第i个位置上的苹果交换。
5. 复杂度分析
- 时间复杂度:O(N²)。无论数组原来是乱序还是已经排好序的,每次找最小值都必须老老实实把剩下的元素全看一遍。所以最好、最坏、平均时间复杂度全都是 O(N²)。
- 空间复杂度:O(1)。只需要几个常数级别的额外变量(如
minIndex,temp)。 - 稳定性:不稳定。举个例子:序列
[5, 5, 2]。第一次找最小值找到2,和第一个5交换,变成了[2, 5, 5]。原本在前面的那个5被换到了后面,相对顺序被破坏了。