← 目录 / 算法文档 · 模块三 简单排序 / 3.2 选择排序

3.2 选择排序

每一轮都先找出"剩下里面最小的那个",再把它放到该在的位置上——交换次数比冒泡排序少得多。

本页目录
① 什么是选择排序

选择排序的思路换了个角度:冒泡排序是"相邻的两个比一比,谁大谁往后";选择排序则是每一轮先把剩下还没排好的部分整个看一遍,找出其中最小的那个,然后把它和这一轮"应该放的位置"交换。第一轮找出整个数组的最小值放到第 1 位,第二轮在剩下的部分里找最小值放到第 2 位,以此类推。

② 基础写法:找最小值放到前面

还是用 {5, 2, 8, 1, 9} 演示。外层循环控制"现在要确定第几个位置",内层循环负责"在剩下的范围里找最小值的下标":

C++ · 选择排序
1void SelectionSort(int arr[], int n)
2{
3 for (int i = 0; i < n - 1; i++) // 确定第 i 个位置该放谁
4 {
5 int minIndex = i; // 先假设当前位置就是最小值
6 for (int j = i + 1; j < n; j++) // 在剩下的部分里找真正的最小值
7 {
8 if (arr[j] < arr[minIndex])
9 {
10 minIndex = j;
11 }
12 }
13 int temp = arr[i]; // 把找到的最小值换到第 i 位
14 arr[i] = arr[minIndex];
15 arr[minIndex] = temp;
16 }
17}
💡
注意交换发生在内层循环结束之后,不是循环内部。内层循环只负责"找位置"(不断更新 minIndex,但不真正移动任何数据),找完一整轮才做一次交换——这是选择排序和冒泡排序最大的写法差异:冒泡是"边比边换",选择是"先找全,再换一次"。
{5, 2, 8, 1, 9} 选择排序的完整过程
原数组
5
2
8
1
9
第 1 轮:在 5,2,8,1,9 里找最小值——是下标3的 1,和下标0交换
1
2
8
5
9
第 2 轮:在 2,8,5,9 里找最小值——是下标1的 2,本来就在对的位置,交换后不变
1
2
8
5
9
第 3 轮:在 8,5,9 里找最小值——是下标3的 5,和下标2交换
1
2
5
8
9
第 4 轮:在 8,9 里找最小值——是下标3的 8,本来就在对的位置
1
2
5
8
9
橙色格子是这一轮刚刚归位的最小值,绿色格子是之前已经归位、不会再变动的数。注意第 2 轮和第 4 轮"找到的最小值"正好已经待在该在的位置上——代码里仍然会执行交换语句,只是把同一个数和自己换了一下,没有实际效果。
③ 和冒泡排序的对比:交换次数更少

冒泡排序每发现一对顺序错误的相邻数,就立刻交换一次——一轮下来可能交换很多次。选择排序则不一样:每一轮,无论内层循环比较了多少次,最多只会真正交换一次(找到最小值之后,和当前位置换一下)。如果"交换"这个操作的代价比"比较"更高(比如交换的是很大的结构体,不只是几个整数),选择排序往往更划算。

20 个随机数排序 —— 两种算法的交换次数对比
冒泡排序
112 次
选择排序
15 次
同样的 20 个随机数,冒泡排序交换了 112 次,选择排序只交换了 15 次(最多 n-1=19 次,部分轮次"最小值已经在对的位置"时甚至不需要真正移动)。不过两者的比较次数是一样多的(都是约 n²/2 次)——选择排序省的是交换,不是比较。
④ 完整代码与对比
排序方式比较次数交换次数时间复杂度
冒泡排序约 n²/2最坏约 n²/2,最好 0O(n²),最好情况可优化到 O(n)
选择排序约 n²/2固定最多 n-1 次O(n²),无法用"提前结束"优化
⚠️
选择排序没有"提前结束"这种优化。即使数组已经排好序了,选择排序依然要把每一轮的"找最小值"老老实实做一遍(虽然每次都不需要真正交换),比较次数没法像冒泡排序那样在最好情况下降到 O(n)——这是选择排序明显的劣势:它的运行时间几乎不受数据原始顺序的影响,好情况和坏情况差别不大。