CC++ & Algorithm

冒泡排序:像汽水泡泡一样把数字排好队

困难4
语言版本:C++Python
概述:冒泡排序是一种简单的排序算法,通过反复比较相邻元素并交换,让较大的数像汽水里的气泡一样慢慢“浮”到数组的末尾。

冒泡排序:像汽水泡泡一样把数字排好队

想象一下,你有一杯汽水,里面有很多小气泡。气泡小的会慢慢往上浮,大的则留在下面。冒泡排序的思路就和这个很像——我们要把一堆乱糟糟的数字从小到大排好,每次比较相邻的两个数,如果前面的数比后面的大,就交换它们。这样一轮下来,最大的数就像最大的气泡一样,慢慢“浮”到了最右边(末尾)。然后我们忽略最后一个已排好的数,再对剩下的数重复同样的操作,直到所有数都排好。


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-ii=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 环境,亲手试试冒泡排序吧!把数字换成自己的考试成绩、零花钱金额或者游戏得分,看看排序结果如何。

例题精讲

1单选题

在冒泡排序中,对长度为5的序列进行升序排序,若初始状态完全逆序(如[5,4,3,2,1]),且未进行任何优化,则排序过程中总共需要进行多少次比较?

A10
B15
C20
D25
2判断题

冒泡排序是一种稳定的排序算法。

3填空题
以下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
4单选题

对冒泡排序进行优化时,常加入一个布尔变量 exchanged(或 swapped ),其作用是什么?

A记录交换的总次数
B判断某一趟是否发生了交换,如果未发生则提前结束排序
C控制比较的方向(从左到右或从右到左)
D标记当前轮次中最后一次交换的位置,以缩小下一轮比较范围
5判断题

在冒泡排序(升序)中,每一趟排序都会将一个当前未排序部分的最大元素放到正确的位置(即未排序部分的末尾)。