> **GESP等级：四级 | 考级考点：GESP Python 四级**

# 第13节：冒泡排序——泡泡往上冒，大的往后靠

---

## 一、课程概览

| 项目 | 内容 |
|------|------|
| 课程名称 | 冒泡排序——气泡往上冒 |
| 课程时长 | 60分钟 |
| 教学形式 | 故事导入 + 动作模拟 + turtle可视化 + 动手编程 |
| 核心目标 | 理解冒泡排序的思想和代码实现，建立排序的直观感受 |

### 教学目标

| 维度 | 目标描述 |
|------|----------|
| 知识目标 | 理解冒泡排序的原理：相邻元素两两比较，大的往后交换 |
| 技能目标 | 能够写出冒泡排序的Python代码，用turtle画出排序动画 |
| 思维目标 | 建立"比较-交换"的编程思维，理解循环嵌套的应用 |
| 情感目标 | 感受算法的趣味性，不怕出错，享受排好序的成就感 |

### 教学重点与难点

| 类型 | 内容 |
|------|------|
| 重点 | 冒泡排序的两两比较和交换逻辑 |
| 难点 | 外层循环和内层循环的次数关系（n-1轮，n-1-i次） |
| 易错点 | 索引不要越界，比较时用[i]和[i+1] |

---

## 二、教学准备

### 教具准备
1. 电脑+Python环境（已安装turtle库）
2. 投影仪/大屏幕（展示排序动画）
3. 数字卡片：写有5,3,1,4,2的大卡片
4. 彩色磁贴/贴纸（给柱子涂色用）
5. 身高尺（选几个孩子现场比身高）
6. 气泡图海报（画一个大泡泡往上冒）

### 课前准备
- 打开IDLE或代码编辑器，提前写好turtle画布模板
- 测试冒泡排序动画代码，确保运行正常
- 打印数字卡片（准备两套）
- 准备小奖品（贴纸/小零食用于小测环节）

---

## 三、教学过程（60分钟）

---

### 第一部分：导入环节（10分钟）——气泡往上冒

#### 1.1 故事引入："水底的气泡"（3分钟）

**老师讲**：
"小朋友们，你们有没有看过水里的气泡呀？"
"想象一下，你拿一根吸管往水里吹气——咕噜咕噜~泡泡从水底往上升！"
"诶，你们发现没有？**小的气泡跑得快，先到水面上；大的气泡比较重，跑得慢**。"

"排序算法里有一个特别有趣的算法，就叫**冒泡排序**！
它就像水里的气泡一样——小的数就像小气泡，轻轻松松往前跑；
大的数就像大气泡，拖拖拉拉在后面。"

**提问互动**：
- 问：水里气泡往哪个方向跑？（答：往上跑）
- 问：小的气泡跑得快还是慢？（答：快）
- 问：大的气泡呢？（答：慢）

#### 1.2 身体游戏：身高排队（5分钟）

**活动设计**：

> **第一步：选5个孩子上台**

老师选5个身高不同的孩子上台，打乱顺序站成一排。

> **第二步：比身高游戏**

老师说规则：
"现在我们来玩个游戏！从左边开始，**相邻两个小朋友比身高**——
如果左边的小朋友比右边的高，他们两个就交换位置。
高的站到后面去，矮的站到前面来！"

**第一轮演示**：
1. 孩子A和B比：A比B高→交换，A站到B后面
2. 孩子B（原来的A）和C比：B比C高→交换，B站到C后面
3. 孩子C（原来的B）和D比：C比D高→交换，C站到D后面
4. 孩子D（原来的C）和E比：D比E高→交换，D站到E后面

经过第一轮，**最高的同学一定站到了最后**！

> **提问**：最高的同学去哪儿了？（答：到最后面了）
> **总结**：就像最大的气泡冒到了最上面！

**第二轮演示**：
重复上面的过程，但最后一个同学已经是最高的了，不需要再比。
第二轮会把**第二高的同学**送到倒数第二个位置。

> **第三轮、第四轮...直到全部排好**

**身体记忆锚点**："比身高→交换位置"这个动作，就是冒泡排序的核心！

#### 1.3 引出主题（2分钟）

"刚才我们玩的游戏，就是**冒泡排序**！"
"在编程里，冒泡排序就是让数字像泡泡一样，两两比较，大的往后'冒'。"

**板书关键词**：
```
冒泡排序 = 相邻比较 + 大的往后换
```

---

### 第二部分：知识点讲解（20分钟）

---

#### 2.1 冒泡排序的原理（5分钟）

**核心思想**：

> **"相邻两家比身高，高的往后站"**

用数字举例：给出一组数 `[5, 3, 1, 4, 2]`

我们把这5个数当成5个小朋友，每个数字就是他们的身高。

**目标**：从小到大排序 → `[1, 2, 3, 4, 5]`

**怎么排？**
- 从第1个开始，相邻两个比
- 如果左边 > 右边，交换（大的往后走）
- 每一轮结束，最大的数一定在最后

#### 2.2 分步演示（8分钟）

**初始数组**：`[5, 3, 1, 4, 2]`

**第1轮（i=0）**——把最大的5冒到最后

| 比较 | 谁大 | 操作 | 结果 |
|------|------|------|------|
| 5 vs 3 | 5大 | 交换 | [3,5,1,4,2] |
| 5 vs 1 | 5大 | 交换 | [3,1,5,4,2] |
| 5 vs 4 | 5大 | 交换 | [3,1,4,5,2] |
| 5 vs 2 | 5大 | 交换 | [3,1,4,2,5] ✅ 5冒到最后 |

> **观察**：第1轮比较了4次（5个数比较4次），最大的5到了最后。
> 就像最大的气泡冒到了水面上！

**第2轮（i=1）**——把第二大的4冒到倒数第二位

| 比较 | 谁大 | 操作 | 结果 |
|------|------|------|------|
| 3 vs 1 | 3大 | 交换 | [1,3,4,2,5] |
| 3 vs 4 | 4大 | 不换 | [1,3,4,2,5] |
| 4 vs 2 | 4大 | 交换 | [1,3,2,4,5] ✅ 4冒到倒数第二位 |

> **观察**：第2轮只比较了3次。因为最后一个5已经是最大的了，不用再比。

**第3轮（i=2）**——把第三大的3冒到倒数第三位

| 比较 | 谁大 | 操作 | 结果 |
|------|------|------|------|
| 1 vs 3 | 3大 | 不换 | [1,3,2,4,5] |
| 3 vs 2 | 3大 | 交换 | [1,2,3,4,5] ✅ 3冒到倒数第三位 |

> **观察**：第3轮比较了2次。最后两个4和5已经排好了，不用再比。

**第4轮（i=3）**——把第四大的2冒到倒数第四位

| 比较 | 谁大 | 操作 | 结果 |
|------|------|------|------|
| 1 vs 2 | 2大 | 不换 | [1,2,3,4,5] ✅ 全部排好 |

**最终结果**：`[1, 2, 3, 4, 5]` ✨

#### 2.3 规律总结（3分钟）

**观察规律**：
1. **5个数需要4轮**（n个数需要n-1轮）
2. **第1轮比较4次，第2轮比较3次，第3轮比较2次，第4轮比较1次**
3. **每轮的比较次数 = n - 1 - i**（i从0开始）

**为什么越来越少？**
- 因为每轮结束，末尾就多一个排好的大数
- 这些排好的数不用再比了

**板书公式**：
```
外循环：n - 1 轮
内循环：n - 1 - i 次比较
```

#### 2.4 口诀记忆（2分钟）

**口诀一：冒泡口诀**
> "冒泡冒泡，大的往后靠，
> 相邻两两比，大数往后移。"

**口诀二：循环口诀**
> "外层n-1轮，内层n-1-i次，
> 每轮冒一个，大的到末尾。"

**口诀三：判断口诀**
> "如果左边比右边大，
> 交换位置别记差。"

**全班一起念口诀**，配合动作：
- "冒泡冒泡"→双手做泡泡往上飞的动作
- "大的往后靠"→右手往右推
- "相邻两两比"→双手食指指向相邻的两个人
- "大数往后移"→右手画弧线移到后面

#### 2.5 代码讲解（2分钟）

**伪代码**：
```
对于 i 从 0 到 n-2：    # 外循环：n-1轮
    对于 j 从 0 到 n-2-i： # 内循环：每轮比较n-1-i次
        如果 arr[j] > arr[j+1]：  # 相邻比较
            交换 arr[j] 和 arr[j+1]  # 大的往后换
```

**交换的三种写法**：

> **方法一：用临时变量（最常用）**
```python
temp = arr[j]
arr[j] = arr[j+1]
arr[j+1] = temp
```

> **方法二：Python特有写法（最简洁）**
```python
arr[j], arr[j+1] = arr[j+1], arr[j]
```

---

### 第三部分：演示环节（10分钟）——turtle排序动画

#### 3.1 展示动画效果

**老师操作**：
1. 运行冒泡排序可视化代码
2. 屏幕上出现一排彩色柱子，高低不同
3. 点击屏幕开始排序
4. 柱子开始动起来——相邻柱子比较，高的往后移动

**引导观察**：
- "看！这两个柱子（指相邻的）在比身高！"
- "高的柱子往后走了！"
- "最大的那个（红色柱子）像气泡一样跑到最后去了！"

#### 3.2 代码结构说明

**展示关键代码段**：

```python
import turtle
import random

# 画柱子
def draw_bar(x, height, color):
    t.penup()
    t.goto(x, 0)
    t.pendown()
    t.fillcolor(color)
    t.begin_fill()
    for _ in range(2):
        t.forward(30)  # 柱子宽度
        t.left(90)
        t.forward(height * 10)  # 柱子高度
        t.left(90)
    t.end_fill()

# 交换并重画
def swap_and_redraw(arr, i, j):
    arr[i], arr[j] = arr[j], arr[i]
    draw_all(arr)
```

#### 3.3 逐行解释核心排序代码

```python
def bubble_sort(arr):
    n = len(arr)
    for i in range(n-1):          # 外循环：轮数
        for j in range(n-1-i):    # 内循环：每轮比较次数
            if arr[j] > arr[j+1]:  # 相邻比较
                arr[j], arr[j+1] = arr[j+1], arr[j]  # 交换
                draw_all(arr)       # 画出来！
                time.sleep(0.3)     # 等一下，让我们看清过程
```

**解释**：
- `n = len(arr)` → 一共有几个数字
- `for i in range(n-1)` → 需要排n-1轮
- `for j in range(n-1-i)` → 每轮少比一个
- `if arr[j] > arr[j+1]` → 左边比右边大？
- 交换 → 大的往后走
- `draw_all(arr)` → 每次交换都重画，就能看到动画！

---

### 第四部分：实操环节（15分钟）

#### 4.1 任务分解

**任务1：运行冒泡排序代码（3分钟）**
- 打开老师给的代码文件
- 运行代码，看到排序动画
- 尝试修改数字列表，观察不同数据排序

**任务2：修改参数（3分钟）**
- 改变列表中的数据（增加或减少数字）
- 修改柱子颜色
- 修改排序速度（time.sleep的时间）

**任务3：填空完成代码（5分钟）**
老师给出去掉关键部分的代码，让学生填空：

```python
def bubble_sort(arr):
    n = len(arr)
    for i in range(_____):          # 应该填什么？（n-1）
        for j in range(_____):      # 应该填什么？（n-1-i）
            if arr[___] > arr[___]:  # 应该填什么？（j, j+1）
                arr[j], arr[j+1] = arr[j+1], arr[j]
                draw_all(arr)
                time.sleep(0.3)
```

**任务4：增加功能（4分钟）**
- 在排序完成后，让所有柱子变成金色/绿色
- 在控制台打印每一轮的结果
- 思考：怎么修改代码让排序变成从大到小？

#### 4.2 常见错误及纠正

| 错误类型 | 错误代码 | 正确代码 | 解释 |
|----------|----------|----------|------|
| 内循环次数不对 | `range(n-1)` | `range(n-1-i)` | 每轮少比一个 |
| 比较索引不对 | `if arr[i] > arr[i+1]` | `if arr[j] > arr[j+1]` | 应该是j不是i |
| 索引越界 | `if arr[j] > arr[j+1]`但j最大是n-1 | j最大n-2 | 要比较到n-2才不会越界 |
| 忘记重画 | 没调draw_all | 每次交换调draw_all | 否则看不到动画 |

#### 4.3 挑战任务（学有余力的学生）

**挑战1**：把冒泡排序改成**从大到小**排序（降序）
- 提示：把 `>` 改成 `<`

**挑战2**：统计排序一共做了多少次比较
- 提示：加一个计数器变量

**挑战3**：实现"优化冒泡排序"——如果一轮中没有发生交换，说明已经排好了，提前结束
- 提示：加一个 `swapped` 标记变量

---

### 第五部分：小测环节（5分钟）

#### 5.1 选择题（3题）

**第1题**：冒泡排序中，相邻两个数比较，如果左边比右边大，应该怎么做？
- A. 不动
- B. 交换位置 ✅
- C. 删除左边
- D. 把右边变大

**第2题**：有6个数用冒泡排序，需要多少轮？
- A. 5轮 ✅
- B. 6轮
- C. 4轮
- D. 7轮

**第3题**：第2轮（i=1）时，需要比较几次？
- A. n-1次
- B. n-2次 ✅
- C. n次
- D. 1次

#### 5.2 判断题（2题）

**第4题**：冒泡排序中，每轮都能确保最大的数"冒"到最后。（✅）

**第5题**：5个数冒泡排序，第3轮需要比较3次。（❌，应该比较2次）

#### 5.3 思维题（1题）

**第6题**：给出一组数 `[7, 2, 5, 1]`，写出第1轮冒泡排序的过程和结果。

> **答案**：
> 7 vs 2 → 7大，交换 → [2,7,5,1]
> 7 vs 5 → 7大，交换 → [2,5,7,1]
> 7 vs 1 → 7大，交换 → [2,5,1,7]
> 第1轮结果：[2,5,1,7]

#### 5.4 快速抢答

**抢答题**："冒泡排序的口诀是什么？"
- 答案："相邻两家比身高，高的往后站"

**抢答题**："冒泡排序是稳定的还是不稳定的？"
- 答案：稳定的（相等的时候不交换，顺序不变）

---

## 四、课后延伸

### 生活中的冒泡排序
- 老师排队改作业：个子矮的站前面，高的站后面
- 图书馆的书按字母顺序排列：相邻的书比较书名
- 操场上按身高排队：两两比较交换

### 拓展阅读
- 推荐查看Python中 `list.sort()` 和 `sorted()` 的用法
- 了解Python内置排序用的是Timsort（一种更快的排序算法）

### 下节预告
"今天我们学了冒泡排序——像气泡一样往上冒。下节课我们学**选择排序**和**插入排序**，一个是'选秀'，一个是'摸牌'，也很有趣哦！"

---

## 五、板书设计

```
╔══════════════════════════════════════════════╗
║          冒 泡 排 序                          ║
║                                              ║
║  🌊 气泡往上冒，大的往后靠                    ║
║                                              ║
║  原理：相邻两两比，大的往后换                 ║
║                                              ║
║  举例：[5,3,1,4,2] → [1,2,3,4,5]             ║
║                                              ║
║  口诀：                                       ║
║  冒泡冒泡，大的往后靠                         ║
║  相邻两两比，大数往后移                       ║
║  外层n-1轮，内层n-1-i次                       ║
║                                              ║
║  代码：                                       ║
║  for i in range(n-1):                        ║
║    for j in range(n-1-i):                    ║
║      if arr[j] > arr[j+1]:                   ║
║        arr[j], arr[j+1] = arr[j+1], arr[j]  ║
╚══════════════════════════════════════════════╝
```

---

## 六、课程反思

### 教师自评要点
1. 孩子们是否理解"相邻比较"的概念？
2. 身体模拟环节是否活跃有效？
3. turtle动画是否成功吸引注意力？
4. 实操环节学生完成度如何？

### 常见问题应对
- **如果孩子不理解循环嵌套**：用"每一轮"和"每一次"的关系来解释。外层是"轮数"，内层是"每轮比较的次数"
- **如果孩子写代码索引越界**：强调 `j` 最大到 `n-2`，因为要和 `j+1` 比较
- **如果孩子问为什么叫"冒泡"**：重新展示气泡动画，把数字颜色涂成渐变色，像彩色泡泡

### 教学调整建议
- 如果时间充裕：增加"优化冒泡"的讲解（加swapped标记）
- 如果时间紧张：跳过挑战任务，只完成基础任务
- 如果孩子年龄偏小（7-8岁）：重点放在身体模拟和动画观察，代码填空即可
- 如果孩子年龄偏大（9-10岁）：可以深入讲解时间复杂度的概念

---

### 拓展：lambda表达式与排序（⭐ GESP四级真题考点）

**lambda = 匿名函数** —— 一句话写成一个函数，不需要def

```python
# 普通函数
def double(x):
    return x * 2

# lambda写法
double = lambda x: x * 2

print(double(5))  # 10
```

**lambda常用在排序中**：
```python
# 按第二个元素排序
pairs = [(1, 'one'), (3, 'three'), (2, 'two')]
pairs.sort(key=lambda x: x[1])
print(pairs)  # [(1, 'one'), (3, 'three'), (2, 'two')] 按字母排

# filter + lambda
nums = [1, 2, 3, 4, 5, 6]
even = list(filter(lambda x: x % 2 == 0, nums))
print(even)  # [2, 4, 6]

# ⭐ map + lambda（四级2023~2025连续考）
squares = list(map(lambda x: x ** 2, range(10)))
print(squares)  # [0, 1, 4, 9, 16, ..., 81]

# ⭐ max/min + key=lambda
data = [(1, 'a'), (3, 'c'), (2, 'b')]
print(max(data, key=lambda x: x[0]))  # (3, 'c')

# ⭐ sorted + enumerate + lambda（四级202406真题）
lst = [3, 1, 2]
result = [i[0] for i in sorted(enumerate(lst), key=lambda x: x[1])]
print(result)  # [1, 2, 0] 按值排序返回原索引
```

**记忆钩子**："lambda=一句话函数，冒号左边是参数右边是结果"
