冒泡排序:像汽水泡泡一样把数字排好队
困难4冒泡排序:像汽水泡泡一样把数字排好队
想象一下,你有一杯汽水,里面有很多小气泡。气泡小的会慢慢往上浮,大的则留在下面。冒泡排序的思路就和这个很像——我们要把一堆乱糟糟的数字从小到大排好,每次比较相邻的两个数,如果前面的数比后面的大,就交换它们。这样一轮下来,最大的数就像最大的气泡一样,慢慢“浮”到了最右边(末尾)。然后我们忽略最后一个已排好的数,再对剩下的数重复同样的操作,直到所有数都排好。
1. 生活中的“冒泡”场景
假设老师让同学们按身高从矮到高排成一列。小明、小红、小刚、小丽的身高分别是:150cm、140cm、160cm、130cm。老师用“相邻比较交换法”:
-
第一轮:比较小明(150)和小红(140),150>140,交换 → 小红(140)、小明(150)、小刚(160)、小丽(130)
-
比较小明(150)和小刚(160),150<160,不交换 → 小红、小明、小刚、小丽
-
比较小刚(160)和小丽(130),160>130,交换 → 小红、小明、小丽、小刚 第一轮结束,最胖(高)的小刚到了队尾。
-
第二轮:只看前三个:小红(140)、小明(150)、小丽(130)
- 140<150,不交换
- 150>130,交换 → 小红、小丽、小明、小刚 第二轮结束,小明到了倒数第二位。
-
第三轮:比较前两个:小红(140)、小丽(130)
- 140>130,交换 → 小丽、小红、小明、小刚 排序完成!就像气泡上浮一样,大的数一步步“浮”到右边。
2. 冒泡排序的核心步骤
冒泡排序的每个环节都可以拆解成两个循环:
- 外层循环:控制总共需要几轮“冒泡”。如果有 n 个数,最多需要 n-1 轮(因为最后剩下一个数时已经有序)。
- 内层循环:在每一轮中,依次比较相邻的两个数,如果顺序不对就交换。随着轮数增加,已经排好的末尾元素不再参与比较,所以每轮比较次数减少。
用代码表示:
def bubble_sort(arr):
n = len(arr) # 数组长度
# 外层循环:需要 n-1 轮
for i in range(n - 1):
# 内层循环:每轮比较相邻元素,末尾 i 个已排好,所以比较次数为 n-1-i
for j in range(n - 1 - i):
if arr[j] > arr[j + 1]: # 如果前面的数大于后面的数
# 交换
arr[j], arr[j + 1] = arr[j + 1], arr[j]
return arr
3. 画出冒泡排序的“痕迹”
我们一步步看 [5, 3, 8, 1] 的演变过程(每一行代表一次交换或比较后的状态):
初始: [5, 3, 8, 1]
第1轮:
比较5和3 → 交换 → [3, 5, 8, 1]
比较5和8 → 不交换 → [3, 5, 8, 1]
比较8和1 → 交换 → [3, 5, 1, 8] ← 8已到位
第2轮(只处理前3个):
比较3和5 → 不交换 → [3, 5, 1, 8]
比较5和1 → 交换 → [3, 1, 5, 8] ← 5已到位
第3轮(只处理前2个):
比较3和1 → 交换 → [1, 3, 5, 8] ← 全部有序
你可以发现:每一轮结束时,当前未排序部分的最大值会“沉”到右边。就像汽水里最大的气泡先浮到水面。
4. 新手容易犯的错误
- 忘记控制内层循环的范围:如果写成
for j in range(n-1),每次都会比较所有相邻元素,已经排好的末尾元素会被反复比较,虽然不影响正确性,但浪费了时间。正确的写法是range(n-1-i)。 - 交换时直接赋值导致数据丢失:例如写成
arr[j] = arr[j+1]会覆盖原值。一定要用两个变量或Python的元组交换a, b = b, a。 - 认为冒泡排序只能从小到大:其实只要把比较符号
>改成<,就能实现从大到小排序(让小的数“浮”到末尾)。 - 循环条件写错:
for i in range(n)会多出一轮,但内层循环的n-1-i在i=n-1时为0,所以不会报错,但外层循环多一次无意义。最好写成range(n-1)。
5. 完整可运行的代码(带优化)
经典冒泡排序还可以加一个“优化”标记:如果某一轮没有任何交换,说明已经有序,可以提前结束。下面是完整的示例,每行变量都加了中文注释:
def bubble_sort(arr):
n = len(arr) # 数组长度
for i in range(n - 1): # 外层循环:n-1轮
swapped = False # 标记是否发生过交换
for j in range(n - 1 - i): # 内层循环:比较相邻元素
if arr[j] > arr[j + 1]: # 顺序不对就交换
arr[j], arr[j + 1] = arr[j + 1], arr[j] # 交换
swapped = True # 记录发生了交换
if not swapped: # 如果这一轮没交换,说明已经有序
break # 提前结束
return arr
# 测试
numbers = [64, 34, 25, 12, 22, 11, 90]
print("排序前:", numbers) # 排序前: [64, 34, 25, 12, 22, 11, 90]
sorted_numbers = bubble_sort(numbers)
print("排序后:", sorted_numbers) # 排序后: [11, 12, 22, 25, 34, 64, 90]
你可以试着把 numbers 改成自己的一组分数(比如 [88, 72, 93, 65, 80]),看看冒泡排序怎么工作。
6. 冒泡排序的“优缺点”
| 优点 | 缺点 |
|---|---|
| 代码简单,容易理解 | 当数据量大时非常慢(时间复杂度 O(n²)) |
| 稳定排序(相等元素不交换相对位置) | 即使数组已经有序,普通版仍会遍历多次(优化后可避免) |
| 不需要额外存储空间(原地排序) | 适合教学,但不适合处理超过几百个数字的数据 |
7. 相关知识点指引
- 其他简单排序:选择排序(每次找出最小的放到前面)、插入排序(像打牌时整理手牌)。
- 效率比较:冒泡排序和选择排序的时间复杂度都是 O(n²),但插入排序在数据接近有序时速度更快。
- GESP 等级要求:4 级需要掌握冒泡排序的过程和代码,能动手写出并模拟执行。下一步可以学习选择排序和插入排序,它们也是入门排序算法。
- 进阶概念:如果数据量很大,需要用更快的排序,如归并排序(O(n log n))或快速排序。冒泡排序是理解这些高级算法的基础。
现在,打开你的 Python 环境,亲手试试冒泡排序吧!把数字换成自己的考试成绩、零花钱金额或者游戏得分,看看排序结果如何。
例题精讲
在冒泡排序中,对长度为5的序列进行升序排序,若初始状态完全逆序(如[5,4,3,2,1]),且未进行任何优化,则排序过程中总共需要进行多少次比较?
冒泡排序是一种稳定的排序算法。
以下Python代码实现了冒泡排序(升序),请补全内层循环的循环范围。
def bubble_sort(arr):
n = len(arr)
for i in range(n-1):
for j in range(___):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr对冒泡排序进行优化时,常加入一个布尔变量 exchanged(或 swapped ),其作用是什么?
在冒泡排序(升序)中,每一趟排序都会将一个当前未排序部分的最大元素放到正确的位置(即未排序部分的末尾)。