选择排序
简单0选择排序:像整理扑克牌一样给数据排顺序
在生活中,我们经常需要把一堆东西按顺序摆好。比如:把一摞扑克牌按从小到大的顺序排列,或者把全班同学按身高从矮到高排队。你会怎么做?最直接的方法就是——每次找出最小的那个,放到最前面,再从剩下的里面继续找最小的……这样重复操作,直到全部排好。这就是 选择排序 的核心思想。
选择排序是一种简单直观的排序算法。它的工作方式是:将待排序的数组分为“已排序部分”和“未排序部分”两个区域。每一轮从“未排序部分”中找出最小的元素,把它放到“已排序部分”的末尾(也就是和未排序部分第一个元素交换位置),然后重复这个过程,直到所有元素都归位。
一、选择排序的思路:画图更容易懂
假设你有一组数字:[5, 3, 8, 1, 6],想排成从小到大。
- 第一轮:整个数组都是“未排序部分”。我找到最小的数字是
1,它现在在第4个位置(索引3)。把1与第1个位置的5交换,得到[1, 3, 8, 5, 6]。此时第1个位置已经是排好的最小数。 - 第二轮:未排序部分是
[3, 8, 5, 6]。最小的数字是3,它就在第2个位置,不用交换(或者说跟自己交换)。数组不变:[1, 3, 8, 5, 6]。 - 第三轮:未排序部分
[8, 5, 6],最小是5,原来在第4个位置(索引3)。把它与第3个位置的8交换,得到[1, 3, 5, 8, 6]。 - 第四轮:未排序部分
[8, 6],最小是6,与第4个位置的8交换,得到[1, 3, 5, 6, 8]。 - 第五轮:只剩一个数字
8,已经有序,排序结束。
这个过程就像玩扑克牌时,先挑出最小的那张放到左手边,再从剩下的牌里继续挑最小的,挨个放过去。
二、生活中的更多例子
-
整理书包里的课本:你有数学、语文、英语、科学四本书,想按科目首字母(A→Z)排好。你会先找出首字母最小的(比如 C 开头的科学 "science"),放到书包最左边;再从剩下的三本里找首字母最小的(比如 E 开头的英语 "english"),放到第二本的位置……直到排完。
-
运动会按身高排队:老师让你把全班同学按从矮到高排成一列。你可能会先扫一眼全班,找到最矮的同学,拉他站到第一个位置;然后在剩下的人里再找最矮的,站到第二个……这就是选择排序的活人版。
-
水果店整理价格标签:香蕉 2 元、苹果 5 元、橙子 3 元、葡萄 8 元,你想按价格从低到高排列。首先找最便宜的(香蕉2元)放到第一位;剩下中找最便宜的(橙子3元)放到第二位,以此类推。
三、Python代码一步一步拆解
下面这段代码实现了选择排序。注意看每一行的中文注释,它能帮你理解变量在干什么。
def selection_sort(arr):
n = len(arr) # 数组长度
for i in range(n): # i 是当前要放最小元素的位置(已排序部分末尾)
min_index = i # 先假设当前位置的元素就是最小的
for j in range(i + 1, n): # 从 i 后面开始找真正的最小值
if arr[j] < arr[min_index]:
min_index = j # 发现更小的,更新最小元素的索引
# 把找到的最小元素交换到位置 i
arr[i], arr[min_index] = arr[min_index], arr[i]
return arr
# 测试一下
nums = [5, 3, 8, 1, 6]
print(selection_sort(nums)) # 输出 [1, 3, 5, 6, 8]
代码要点解释:
- 外层循环
for i in range(n):控制第几轮。i从0开始,表示这一轮要把最小的数放到索引i的位置。 - 内层循环
for j in range(i+1, n):在i后面(未排序部分)找最小的元素。用min_index记录目前找到的最小值的下标,每找到一个更小的就更新。 - 交换
arr[i], arr[min_index] = arr[min_index], arr[i]:把真正最小的元素和第i位置的元素互换。如果min_index == i,相当于自己和自己交换,无影响。 - 每次循环结束后,前
i+1个元素已经是全局最小的几个,且按顺序排好了。
四、新手容易犯的错误
-
忘记更新
min_index
有些同学写了if arr[j] < arr[i],这样只比较了当前元素与位置i的元素,没有跟踪全局最小。必须用min_index来记录最小的下标,否则可能会错过更后面的小元素。 -
内层循环范围写错
内层循环应该是for j in range(i+1, n),而不是range(i, n)。如果从i开始,会和自己比较一次,虽然不影响结果,但浪费时间。如果写成range(i)则会漏掉后面的元素。 -
交换时误写成
arr[i] = arr[min_index]
只赋值一边会导致数据丢失。必须用两两交换的方式(Python 的 tuple swap 很方便),或者用临时变量。 -
认为每次交换都会改变最小值的位置
实际上min_index在每一轮内循环中更新,最后才交换一次。不是每发现一个更小的就立即交换——那样效率更低。
五、完整可运行的示例(加上中间打印过程)
如果你想亲眼看到排序过程中数组的变化,可以这样写:
def selection_sort_with_steps(arr):
n = len(arr)
print("初始数组:", arr)
for i in range(n):
min_index = i
for j in range(i + 1, n):
if arr[j] < arr[min_index]:
min_index = j
arr[i], arr[min_index] = arr[min_index], arr[i]
print(f"第{i+1}轮后: {arr} (将 {arr[i]} 放到位置 {i})")
return arr
nums = [5, 3, 8, 1, 6]
sorted_nums = selection_sort_with_steps(nums)
# 运行结果会输出每一轮的变化
运行输出:
初始数组: [5, 3, 8, 1, 6]
第1轮后: [1, 3, 8, 5, 6] (将 1 放到位置 0)
第2轮后: [1, 3, 8, 5, 6] (将 3 放到位置 1)
第3轮后: [1, 3, 5, 8, 6] (将 5 放到位置 2)
第4轮后: [1, 3, 5, 6, 8] (将 6 放到位置 3)
第5轮后: [1, 3, 5, 6, 8] (将 8 放到位置 4)
六、时间复杂度和空间复杂度
- 时间复杂度:选择排序需要两层循环,外层执行 n 次,内层平均执行 (n-1)/2 次,所以总比较次数是 n(n-1)/2 ≈ O(n²)。无论数据原本是否有序,它都要比较这么多次(因为每次都要找最小),所以最好的、最坏的和平均时间复杂度都是 O(n²)。
- 空间复杂度:排序过程只使用了几个临时变量(如
min_index、循环变量),没有额外开辟数组,所以空间复杂度为 O(1),属于原地排序。 - 稳定性:选择排序 不稳定。举个例子:数组
[4a, 3, 4b, 1](两个4,用 a、b 区分),第一轮把1换到最前面时,会把4a移到后面,导致原来4a在4b前面的顺序可能被破坏。如果要求稳定排序,可以考虑插入排序或归并排序。
七、相关知识点指引
- 冒泡排序:也是 O(n²) 的简单排序,相邻元素两两比较交换,就像气泡上浮。适合理解比较和交换的过程。
- 插入排序:通过将元素插入到已排序部分的合适位置来排序,玩扑克牌时用这种方法更自然。它在数据基本有序时速度很快。
- 归并排序:采用“分而治之”的思想,把数组不断切分成小段,排序后再合并,时间复杂度 O(n log n),是更高效的算法。
- 选择排序 vs 插入排序:选择排序总是“一定要找到最小才交换”,而插入排序是“一边比较一边向前插入”。插入排序在部分有序时表现更好。
- Python 内置排序:实际写代码时,直接用
sorted()或list.sort()更简单,它们基于 Timsort(一种混合排序),又快又稳定。
想继续学习排序算法的同学,建议按这个顺序:选择排序 → 冒泡排序 → 插入排序 → 快速排序 → 归并排序。每一步都能帮你深入理解算法中的“比较”和“移动”思想。
例题精讲
对于长度为 n 的数组,使用选择排序进行升序排列,总共需要执行多少轮(即外层循环次数)?
选择排序是一种稳定的排序算法。
以下代码实现了选择排序的升序排列,请补充空缺部分。
def selection_sort(arr):
n = len(arr)
for i in range(n - 1):
min_idx = i
for j in range(i + 1, n):
if arr[j] < arr[min_idx]:
___
arr[i], arr[min_idx] = arr[min_idx], arr[i]对数组 [5, 3, 8, 6, 2] 进行第一轮升序选择排序后,数组变为?
以下代码实现选择排序的降序排列(从大到小),请补充空缺处的比较符号。
def selection_sort_desc(arr):
n = len(arr)
for i in range(n - 1):
max_idx = i
for j in range(i + 1, n):
if arr[j] ___ arr[max_idx]:
max_idx = j
arr[i], arr[max_idx] = arr[max_idx], arr[i]