循环结构(六)
综合循环Ⅱ:枚举、搜索与优化

从搬砖问题和找零钱问题,理解“系统尝试所有可能”的程序设计思想

本课核心问题

如果答案不知道在哪里,程序怎样系统地寻找?

枚举

把可能的取值逐个尝试。

约束

只保留满足条件的组合。

优化

删除不必要的枚举,减少搜索范围。

第3课时学习目标

知识目标

  • 理解枚举搜索问题的基本结构
  • 理解多重循环中条件约束的写法
  • 理解标志变量如何控制嵌套循环提前结束

能力目标

  • 能把文字问题转化为变量、范围和约束
  • 能从三重枚举优化为二重枚举
  • 能综合使用嵌套循环、break 和 flag
4.5 / 综合循环结构程序设计
不知道答案时,
先把可能情况列出来
枚举不是“瞎试”,而是在明确范围和约束下系统尝试。
变量范围约束输出

枚举问题四步法

步骤 要回答的问题
1. 定义变量 有哪些未知量?
2. 确定范围 每个未知量可能取哪些值?
3. 写出约束 什么组合才是合法答案?
4. 输出结果 找到后是全部输出,还是只要最优/第一个?
循环负责“尝试”,条件语句负责“筛选”。

医学数据中的枚举思维:查找组合

例如在教学示例中,若要从一组候选测量方案中寻找满足“人数、次数、总记录数”约束的方案,也可以先枚举所有可能组合。

枚举方案A
→
枚举方案B
→
检查约束
→
保留合法方案
教材例4-11
搬砖问题:
从三重枚举到二重枚举
枚举所有组合,再根据约束筛选;能计算出来的变量不必再枚举。
三重循环约束条件优化

教材例4-11 题面描述

📋 题目要求

某工地需要搬运砖块,男人一人搬 3 块,女人一人搬 2 块,小孩两人搬 1 块。
如果想用 \(n\) 人正好搬 \(n\) 块砖,问有哪些搬法?

搬砖问题:先建模,不急着写代码

未知变量

  • men:男人数量
  • women:女人数量
  • children:小孩数量

两个约束

  • 人数:men + women + children == n
  • 砖数:3*men + 2*women + 0.5*children == n

为避免浮点比较,也可把砖数约束整体乘以2。

把约束改写为整数形式

原约束

3m + 2w + 0.5c == n

整体乘以2

6m + 4w + c == 2n

能用整数表达的约束,优先用整数表达,减少浮点比较带来的干扰。

版本1:直接枚举三个变量

cnt = 0;
for (men = 0; men <= n; men++) {
    for (women = 0; women <= n; women++) {
        for (children = 0; children <= n; children++) {
            if (men + women + children == n &&
                6 * men + 4 * women + children == 2 * n) {
                printf("men=%d, women=%d, children=%d\n", men, women, children);
                cnt++;
            }
        }
    }
}

这是最直接、最容易理解的枚举方式。

如果已经知道 men 和 women,还需要枚举 children 吗?

总人数是 n
→
已知 men
→
已知 women
→
children = n - men - women
不要枚举可以计算出来的变量。

版本2:删除可以计算出来的变量

men_limit = n / 3;
women_limit = n / 2;
for (men = 0; men <= men_limit; men++) {
    for (women = 0; women <= women_limit; women++) {
        children = n - men - women;
        if (children >= 0 &&
            6 * men + 4 * women + children == 2 * n) {
            printf("men=%d, women=%d, children=%d\n", men, women, children);
        }
    }
}
优化不是为了炫技,而是让程序少做不必要的尝试。

🤖 AI 协作互动:穷举范围裁剪指导

在用三重循环求解『30 人 50 块钱』百鸡问题时,如果已知 men+women+children=30,如何利用数学关系少写一层循环?请帮我推导用双重循环替代三重循环的约束方程。

💡 课堂互动:引导学生用 AI 验证从 O(N^3) 降低到 O(N^2) 的数学降维思路。

交互演示:枚举搜索空间可视化

🔍 D04-04 · 搬砖问题枚举搜索可视化

本演示工具用于对比搬砖问题中“三重循环暴力穷举”与“优化后二重循环”在尝试次数上的效率差异:

  • 搜索空间对比:直观展现三重循环全空间扫描与利用数学关系少写一层循环的剪枝过程。
  • 单步与自动执行:逐行列出当前组合 (men, women, children),并实时判定约束条件。
  • 合法解高亮突出:当满足总人数与总钱数方程时高亮显示解,并实时统计总尝试次数。
教材例4-12
找零钱问题:
找到合适组合后停止
当目标是“最少硬币数”时,可以设计搜索顺序,并用标志变量提前结束。
嵌套循环flag提前停止

教材例4-12 题面描述

📋 题目要求

有足够数量的 5 分、2 分和 1 分硬币,现在要用这些硬币来支付一笔小于 1 元的零钱 money,问至少要用多少个硬币?
输入零钱 money,输出硬币的总数量和相应面额的硬币数量。

找零钱问题:变量、范围、目标

变量

n5、n2、n1 分别表示5分、2分、1分硬币数量。

约束

5*n5 + 2*n2 + n1 == money

目标

硬币总数尽量少,优先尝试面值大的硬币。

为什么从大面值开始枚举?

尽量多用5分
→
再尝试2分
→
最后补1分
→
先找到的通常更少
搜索顺序本身也表达了算法策略。

使用标志变量控制嵌套循环

flag = 1;
for (n5 = money / 5; n5 >= 0 && flag == 1; n5--) {
    for (n2 = (money - n5 * 5) / 2; n2 >= 0 && flag == 1; n2--) {
        for (n1 = money - n5 * 5 - n2 * 2; n1 >= 0 && flag == 1; n1--) {
            if (n5 * 5 + n2 * 2 + n1 == money) {
                printf("n5=%d, n2=%d, n1=%d, total=%d\n", n5, n2, n1, n5+n2+n1);
                flag = 0;
            }
        }
    }
}

这里的 flag 用于让多层循环都感知“已经找到答案”。

为什么只写一个 break 不够?

单层 break

只能跳出当前所在的一层循环。

flag 控制多层循环

每一层循环条件都检查 flag == 1,找到答案后统一停止。

当嵌套循环有多个出口时,标志变量可以把“是否继续搜索”传递给外层循环。

🤖 AI 协作互动:多重循环提前退出策略

在 C 语言中 break 只能跳出当前一层循环。如果在两层嵌套循环中找到解并想立刻退出整个多重循环,使用 flag 标志变量配合两级 break 的标准模式是什么?请给出模板。

💡 课堂互动:对比 AI 给出的 flag 标记法与 goto 语句退出法在代码可读性上的优势。

找零钱搜索树

找零钱搜索树
第四次课总结
循环不是一种语句,
而是一种问题组织方式
从条件控制、状态迭代到枚举搜索,循环帮助程序系统地处理重复。
条件状态嵌套搜索

循环结构选择图

要重复执行
→
次数明确?
for
→
次数不明但条件明确?
while
→
至少执行一次?
do-while

提前结束

break

跳过本轮

continue

两级重复

嵌套循环

本章常见循环模型地图

问题类型 核心变量 典型题
输入值控制 score/pulse 一批数据平均值
计算值控制 item, eps 格里高里公式
状态迭代 x1, x2, x Fibonacci
枚举筛选 k, flag 区间素数
组合搜索 多个枚举变量 搬砖、找零钱

🤖 AI 协作互动:循环不变量与性能提速

请帮我检查这段嵌套循环代码:循环体内部是否有可以在外层提前计算的重复表达式(如 strlen(str) 或固定系数乘积)?请解释提取『循环不变量』提升性能的方法。

💡 课堂互动:同桌讨论 AI 提炼出的不变量优化原则,并在课件总结图中定位适用场景。

课后迁移练习

基础练习

  • 补全阶乘和程序
  • 跟踪Fibonacci变量变化
  • 修改区间素数的输出格式

提高练习

  • 将搬砖问题改为输出方案总数
  • 比较三重循环和二重循环尝试次数
  • 为找零钱程序设计5组边界测试

写循环前,先画出重复关系。