要学习的知识点
- 冒泡排序
- 选择排序
- 插入排序
- 函数参数和返回元组
- 比较次数统计
题目
练习 11:实现冒泡、选择、插入排序,并统计元素比较次数。
输入:第一行 n,第二行 n 个整数。
比较次数只统计两个元素之间的大小比较,不统计下标边界判断。
完整代码
以下内容与 ex11.py 一致,可对照右侧讲解阅读。
"""练习 11:实现冒泡、选择、插入排序,并统计元素比较次数。
输入:第一行 n,第二行 n 个整数。
比较次数只统计两个元素之间的大小比较,不统计下标边界判断。
学习说明:
- 三个函数都先复制 data,避免排序时改动原列表;返回“排序结果, 比较次数”。
- 冒泡排序:相邻元素逆序就交换;每一轮把尚未排好的一端最大值放到末尾。
- 选择排序:在未排序部分找到最小值,放到本轮开头。
- 插入排序:取出一个 key,在前面已排序部分中向左寻找插入位置。
- comparisons 只在实际比较两个数字时加 1;主程序对三种算法使用同一份输入并分别打印。
"""
def bubble_sort(data):
# 复制输入列表;比较次数从 0 开始。
values = data[:]
comparisons = 0
# end 是本轮尚未排好部分的右边界,每轮都向左缩小。
for end in range(len(values) - 1, 0, -1):
swapped = False
# 从左到右检查相邻元素,把较大的值逐步交换到右侧。
for index in range(end):
comparisons += 1
if values[index] > values[index + 1]:
values[index], values[index + 1] = values[index + 1], values[index]
swapped = True
if not swapped:
break
return values, comparisons
def selection_sort(data):
# 每轮选出剩余元素中的最小值,放到 start 位置。
values = data[:]
comparisons = 0
for start in range(len(values) - 1):
minimum = start
for index in range(start + 1, len(values)):
comparisons += 1
if values[index] < values[minimum]:
minimum = index
values[start], values[minimum] = values[minimum], values[start]
return values, comparisons
def insertion_sort(data):
# 左侧部分在每轮开始时已经有序;把下一个 key 插入正确位置。
values = data[:]
comparisons = 0
for index in range(1, len(values)):
key = values[index]
position = index - 1
while position >= 0:
comparisons += 1
if values[position] > key:
values[position + 1] = values[position]
position -= 1
else:
break
values[position + 1] = key
return values, comparisons
n = int(input())
values = list(map(int, input().split()))[:n]
# 将函数与显示名称配对,逐个运行三种排序。
for name, sort_function in (
("bubble", bubble_sort),
("selection", selection_sort),
("insertion", insertion_sort),
):
result, comparisons = sort_function(values)
print(name + ":", *result)
print("comparisons:", comparisons)
进阶写法
这版与初学版解决同一道题,使用更精炼的语法或标准库工具。
进阶源码:ex11_adv.py
"""练习 11 进阶版:保留三种排序算法,去掉逐行教学注释。"""
def bubble_sort(data):
values = data[:]
comparisons = 0
for end in range(len(values) - 1, 0, -1):
swapped = False
for index in range(end):
comparisons += 1
if values[index] > values[index + 1]:
values[index], values[index + 1] = values[index + 1], values[index]
swapped = True
if not swapped:
break
return values, comparisons
def selection_sort(data):
values = data[:]
comparisons = 0
for start in range(len(values) - 1):
minimum = min(range(start, len(values)), key=values.__getitem__)
comparisons += len(values) - start - 1
values[start], values[minimum] = values[minimum], values[start]
return values, comparisons
def insertion_sort(data):
values = data[:]
comparisons = 0
for index in range(1, len(values)):
key = values[index]
position = index - 1
while position >= 0:
comparisons += 1
if values[position] <= key:
break
values[position + 1] = values[position]
position -= 1
values[position + 1] = key
return values, comparisons
n = int(input())
values = list(map(int, input().split()))[:n]
for name, sort_function in (("bubble", bubble_sort), ("selection", selection_sort), ("insertion", insertion_sort)):
result, comparisons = sort_function(values)
print(name + ":", *result)
print("comparisons:", comparisons)
三种算法仍然手动实现。选择排序使用 min(..., key=values.__getitem__) 找最小值下标;比较次数按该轮其余元素数累计。插入排序用 <= 表示“已到达插入位置”。
运行结果
示例输入:
5
4 2 5 1 3程序输出:
bubble: 1 2 3 4 5
comparisons: 10
selection: 1 2 3 4 5
comparisons: 10
insertion: 1 2 3 4 5
comparisons: 8逐个认识函数
下表根据程序中实际调用的函数和方法整理;自定义函数的实现与算法细节见后续说明。
| 函数形式 | 功能与返回值 | 注意事项 |
|---|---|---|
input(prompt='') | 读取一行文本,返回 str;若提供 prompt 会先显示提示。 | 返回值默认是字符串,计算前通常要做类型转换。 |
str.split(sep=None) | 按分隔符拆分字符串,返回字符串列表。 | 默认按连续空白拆分并忽略首尾空白。 |
int(value, base=10) | 把整数文本或数值转换为整数。 | int('3.5') 不合法;按指定进制解析时可传 base。 |
len(container) | 返回字符串、列表、元组等对象的元素个数。 | 整数等没有长度的对象不能传入。 |
list(iterable=()) | 从可迭代对象创建列表。 | 列表有顺序、可修改,允许重复元素。 |
map(function, iterable) | 逐个应用函数,返回惰性迭代器。 | 常与 list() 一起使用以立即取得结果。 |
print(*objects, sep=' ', end='\n') | 把对象转换为文本显示;返回 None。 | 默认空格分隔并在末尾换行。 |
range(start, stop, step=1) | 产生整数序列供循环或索引使用。 | stop 不包含在序列里。 |
sort_function(data) | 调用当前变量所引用的排序函数,返回排序列表和比较次数。 | 变量保存的是函数本身;括号表示真正调用函数。 |
算法原理
冒泡排序
从左到右比较相邻两个数,若左边较大就交换。像气泡一样,大数逐步“冒”到右端。每轮后,右侧一个位置确定。若一整轮没有交换,列表已经有序,可提前结束。
选择排序
看未整理区域,找出最小的数,放到最左边空位。再从剩下的区域重复。它会扫描剩余部分,但交换次数通常不多。
插入排序
把左侧看作已经排好。拿一个新数 key,向左比较,把比 key 大的数字右移,最后把 key 放进空位。像整理手里的扑克牌。
| 排序 | 最好理解的动作 | 常见时间量级 |
|---|---|---|
| 冒泡 | 交换相邻逆序对 | 最坏 O(n²),已有序时本实现可提前结束 |
| 选择 | 每轮寻找最小值 | O(n²) |
| 插入 | 把新元素插入有序前缀 | 最坏 O(n²),小规模或近乎有序时常较好 |
本程序自定义函数
| 函数 | 参数 | 返回值 | 注意点 |
|---|---|---|---|
bubble_sort(data) | 一个可切片的列表。 | 二元组:(排好序的新列表, 元素比较次数)。 | 复制列表,不改调用者的原数据;无交换时 break。 |
selection_sort(data) | 待排序列表。 | 排序结果和比较次数。 | 每轮记录最小元素下标 minimum,再与 start 位置交换。 |
insertion_sort(data) | 待排序列表。 | 排序结果和比较次数。 | key 暂存当前待插入元素,position 向左移动。 |
使用到的内置工具和语法
data[:]是列表切片复制;空切片边界表示复制全部元素。len(values)返回元素数;无元素时为 0。range(start, stop)产生 stop 不包含的整数序列,用来控制轮次和下标。return values, comparisons返回一个二元组,可写作result, comparisons = ...解包。for name, sort_function in (...)逐项取出名称和函数对象,再调用sort_function(values)。
比较次数怎么数
程序只在执行 values[index] > values[index+1] 或 values[index] > key 这类数值比较前把计数加 1。比如比较 4 和 2 是一次;检查 position >= 0 是下标边界判断,不计入题目定义的元素比较次数。
插入排序循环可能因边界结束,也可能因找到合适位置而 break;因此它的比较次数会随输入顺序变化。选择排序会完整扫描每个剩余区域,比较数通常固定为 n(n-1)/2。
先用 3 个数手动走一轮,再逐行跟踪 index、key、minimum 和 comparisons。不要只看最终排序结果。
代码如何运行
- 先按左侧题目给出的格式输入数据;程序通过 input() 读取,并在需要时转换数据类型。
- 程序自上而下执行语句;遇到条件、循环或函数调用时,按照右侧算法说明更新变量和处理数据。
- 最后由 print() 输出计算结果。可将左侧示例输入与输出逐项对照。
先自己跟踪一组小数据,记录循环前后变量的值;这比只看最终答案更容易理解程序。
自己动手试一试
分别输入已排序、逆序、重复元素和空列表,比较三种算法的比较次数。
先在 lx11.py 中独立完成,再运行参考程序比较结果。修改程序时一次只改一个条件或步骤。