CC++ & Algorithm

选择排序

简单0
语言版本:C++
概述:用找最小牌的方式学会选择排序,Python代码帮你理解。

选择排序:像整理扑克牌一样给数据排顺序

在生活中,我们经常需要把一堆东西按顺序摆好。比如:把一摞扑克牌按从小到大的顺序排列,或者把全班同学按身高从矮到高排队。你会怎么做?最直接的方法就是——每次找出最小的那个,放到最前面,再从剩下的里面继续找最小的……这样重复操作,直到全部排好。这就是 选择排序 的核心思想。

选择排序是一种简单直观的排序算法。它的工作方式是:将待排序的数组分为“已排序部分”和“未排序部分”两个区域。每一轮从“未排序部分”中找出最小的元素,把它放到“已排序部分”的末尾(也就是和未排序部分第一个元素交换位置),然后重复这个过程,直到所有元素都归位。


一、选择排序的思路:画图更容易懂

假设你有一组数字:[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,已经有序,排序结束。

这个过程就像玩扑克牌时,先挑出最小的那张放到左手边,再从剩下的牌里继续挑最小的,挨个放过去。


二、生活中的更多例子

  1. 整理书包里的课本:你有数学、语文、英语、科学四本书,想按科目首字母(A→Z)排好。你会先找出首字母最小的(比如 C 开头的科学 "science"),放到书包最左边;再从剩下的三本里找首字母最小的(比如 E 开头的英语 "english"),放到第二本的位置……直到排完。

  2. 运动会按身高排队:老师让你把全班同学按从矮到高排成一列。你可能会先扫一眼全班,找到最矮的同学,拉他站到第一个位置;然后在剩下的人里再找最矮的,站到第二个……这就是选择排序的活人版。

  3. 水果店整理价格标签:香蕉 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 个元素已经是全局最小的几个,且按顺序排好了。

四、新手容易犯的错误

  1. 忘记更新 min_index
    有些同学写了 if arr[j] < arr[i],这样只比较了当前元素与位置 i 的元素,没有跟踪全局最小。必须用 min_index 来记录最小的下标,否则可能会错过更后面的小元素。

  2. 内层循环范围写错
    内层循环应该是 for j in range(i+1, n),而不是 range(i, n)。如果从 i 开始,会和自己比较一次,虽然不影响结果,但浪费时间。如果写成 range(i) 则会漏掉后面的元素。

  3. 交换时误写成 arr[i] = arr[min_index]
    只赋值一边会导致数据丢失。必须用两两交换的方式(Python 的 tuple swap 很方便),或者用临时变量。

  4. 认为每次交换都会改变最小值的位置
    实际上 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 移到后面,导致原来 4a4b 前面的顺序可能被破坏。如果要求稳定排序,可以考虑插入排序或归并排序。

七、相关知识点指引

  • 冒泡排序:也是 O(n²) 的简单排序,相邻元素两两比较交换,就像气泡上浮。适合理解比较和交换的过程。
  • 插入排序:通过将元素插入到已排序部分的合适位置来排序,玩扑克牌时用这种方法更自然。它在数据基本有序时速度很快。
  • 归并排序:采用“分而治之”的思想,把数组不断切分成小段,排序后再合并,时间复杂度 O(n log n),是更高效的算法。
  • 选择排序 vs 插入排序:选择排序总是“一定要找到最小才交换”,而插入排序是“一边比较一边向前插入”。插入排序在部分有序时表现更好。
  • Python 内置排序:实际写代码时,直接用 sorted()list.sort() 更简单,它们基于 Timsort(一种混合排序),又快又稳定。

想继续学习排序算法的同学,建议按这个顺序:选择排序 → 冒泡排序 → 插入排序 → 快速排序 → 归并排序。每一步都能帮你深入理解算法中的“比较”和“移动”思想。

例题精讲

1单选题

对于长度为 n 的数组,使用选择排序进行升序排列,总共需要执行多少轮(即外层循环次数)?

An-1
Bn
Cn/2
Dlog n
2判断题

选择排序是一种稳定的排序算法。

3填空题
以下代码实现了选择排序的升序排列,请补充空缺部分。
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]
4单选题

对数组 [5, 3, 8, 6, 2] 进行第一轮升序选择排序后,数组变为?

A[2, 3, 8, 6, 5]
B[2, 5, 8, 6, 3]
C[3, 5, 8, 6, 2]
D[2, 3, 5, 6, 8]
5填空题
以下代码实现选择排序的降序排列(从大到小),请补充空缺处的比较符号。
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]