1. 什么是选择排序?
先说一下选择排序的概念,选择排序就是从一堆数组里面选择一个最大(最小)的,放在有序的数组里。
选择排序(Selection Sort)是一种简单直观的排序算法。它的基本思想是从未排序的元素中找到最小(或最大)的元素,然后将其放到已排序序列的末尾(或开头),以此不断缩小未排序序列的范围,直到所有元素都排序完成为止。
选择排序的步骤如下:
- 首先,在未排序序列中找到最小(或最大)的元素,并记录其位置。
- 将该最小(或最大)元素与未排序序列的第一个元素交换位置,将其放到已排序序列的末尾(或开头)。
- 然后,缩小未排序序列的范围,从剩余的未排序元素中继续找到最小(或最大)的元素,并重复步骤 2,直到所有元素都排序完成。
以上摘自 ChatGPT。
本质上就 2 个步骤
- 找最大的 value
- 放在最前面或者最后面
但是给我们的是前面无序的怎么办呢?我们就假定第一个就是有序的。就是这个一个。然后跟第一个对比。
2. 经典写法
这个写法是所有AI里面都会优先写的写法,直接看代码进行学习会更快一点。
def selection_sort(arr):
n = len(arr)
# 外层循环:决定我们要把“最小值”放在哪个位置
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
# 走出内层循环后,min_index 指向的就是真正的最小值
# 只有当最小值不是 i 自己时,才进行交换
if min_index != i:
arr[i], arr[min_index] = arr[min_index], arr[i]
return arr
# 测试
data = [29, 10, 14, 37, 13]
print(f"排序后: {selection_sort(data)}")
难点
选择排序难点其实和冒泡差不多。
到底需要排序多少次?
假如是这样一个数组 [5,66,4,9,2]。那么就要比较长度-1 次。也就是 4 次。因为要去掉自身。
那么每一次从哪里开始呢?
从未排序的开始,i 之前都是排好序的,i 之后都没排序好。所以也就是 j = i + 1 这个时候
一次对比排序之后会发生什么?
有序区从前面开始确定一个。假定array[0]是有序区,第一次排序array[0]有序,第二次排序array[1]有序。
一次对比排序之后能确定什么?
只能确定minIndex,最小的索引,然后交换。每次比较的目的是拿到索引。选择排序的核心哲学是:“谋定而后动”。 在内层循环里,我们只记下标,绝不交换。等内层循环全跑完了,确定了谁是真正的最小值,才在最后做 1 次交换。
为什么要if min_index != i:这样比较? 去掉可以吗?
简单来说,加这一步 if min_index != i: 是为了避免“自己和自己交换”的无用功。如果不加这个 if 会怎样?
如果你去掉了这个判断,代码依然能正常运行,不会报错。但它会默默执行这样一行代码。虽然在 Python 里看着只是简单的变量重新赋值,但在计算机底层,“写内存(赋值/交换)”的开销远大于“读内存(比较)”。
arr[0], arr[0] = arr[0], arr[0]
下面是我以前写的便于理解的写法。可以看一下,其实本质也差不多。
"""
1. find_smallest_index()辅助方法实现找到最小的值
2. select_sort()才是排序本体
"""
# 找最小
def find_smallest_index(number):
# 把索引第1个当最小
# 记录下索引
small_number = number[0]
small_index = 0
# 开始遍历找到最小的那个
for i in range(len(number)):
# 你竟然比我假想的最小的还小,好吧。我称你为最小。
# 记录下最小的value和index
# 然后接着循环循环,一直找到最小的那个的index!
if number[i] < small_number:
small_number = number[i]
small_index = i
return small_index
def select_sort(arr):
new_list = []
for i in range(len(arr)-1):
small_index = find_smallest_index(arr)
new_list.append(arr.pop(small_index))
return new_list
print(select_sort([5,6,90,2,89]))
# result
# [2, 5, 6, 89, 90]
3. 优化写法
选择排序(Selection Sort)的一个尴尬之处在于:无论数组是乱序的还是已经排好序的,它都会固执地跑完整个 $O(n^2)$ 过程。它的优化空间比冒泡排序小,但确实有一个非常经典的变体,叫做二元选择排序(Bidirectional Selection Sort)。
其实就是双指针,每一次找到一个大的,同时也找一个小的。
普通选择排序每轮只找一个最小值。
优化逻辑:既然都要扫描一遍数组,为什么不顺便把最大值也找出来呢?
- 每一轮扫描,同时锁定
min_index和max_index。 - 把最小值换到左边,把最大值换到右边。
- 这样外层循环只需要跑 n / 2 次
def dual_selection_sort_while(arr):
n = len(arr)
left = 0
right = n - 1
while left < right:
min_index = left
max_index = right
# 寻找当前未排序区间 [left, right] 内的最大值和最小值
# 注意 Python 的 range 是左闭右开,所以要写 right + 1 才能包含 right
for i in range(left, right + 1):
if arr[i] < arr[min_index]:
min_index = i
if arr[i] > arr[max_index]:
max_index = i
# 1. 先把最小值交换到左边界
arr[min_index], arr[left] = arr[left], arr[min_index]
# 2. 关键陷阱处理:如果原本的最大值就坐在 left 的位置上,
# 经过上面那一步,它已经被一脚踢到了 min_index 的位置。
# 所以必须把 max_index 的指针重新指向 min_index,把它“找回来”。
if max_index == left:
max_index = min_index
# 3. 再把最大值交换到右边界
arr[max_index], arr[right] = arr[right], arr[max_index]
# 缩小未排序的范围
left += 1
right -= 1
return arr
# 测试一下
data = [3, 5, 1, 9, 7, 2, 8, 4]
print(f"排序前: {data}")
print(f"排序后: {dual_selection_sort_while(data)}")
上面代码比较难理解的地方在于2个
为什么要if max_index == i: 这个你可以写一个例子
[155, 3, -7, 5, 2]
# 找到了最大最小 maxIndex:0,left:0,minIndex:2,right:4
arr[i], arr[min_index] = arr[min_index], arr[i]
# 当你走完了这一步之后 数组成了这个样子
[-7, 3, 155, 5, 2]
# 你会发现此时-7在index为0的上面了
# if max_index == i,直接就
arr[max_index], arr[right] = arr[right], arr[max_index]
# 相当于就是 索引为0和4交换
[2, 3, 155, 5, -7]
# 很不幸,你的-7会被调到最右边,这样就有了bug
# 那怎么解决呢?
# 就是如果 if max_index == i 是相等的时候 那么你必须将
max_index = min_index
4. 递归写法(不用记)
如果你觉得选择排序找最小值太慢O(n),那么“进化版”的选择排序就是堆排序。
普通选择:用肉眼看,时间复杂度O(n)
堆排序:把数组建成一个“大顶堆”或“小顶堆”,找最值的时间复杂度降到了 logN
这是选择排序在工程实践中唯一的真正“大杀器”进化版。
def recursive_selection_sort(arr, start=0):
n = len(arr)
# 递归终止条件
if start >= n - 1:
return
# 这一层的任务:找到剩余部分的最小值并放好
min_idx = start
for j in range(start + 1, n):
if arr[j] < arr[min_idx]:
min_idx = j
arr[start], arr[min_idx] = arr[min_idx], arr[start]
# 递归处理剩下的部分
recursive_selection_sort(arr, start + 1)
🔔 直接看提示词
如果你觉得上面的你都没看懂,或者太长不看。可以直接到这里。复制一下提示词。
什么是选择排序?请给我一个最常见的实现。
选择排序有什么优化写法?请浅显易懂抽丝剥茧的给我讲述一下优化写法。
xxxx这个写法能否有具体的例子给我一步步的讲解一下,包括代码的难点。
我想知道这个代码为什么不可以是xxxx