CC++ & Algorithm

循环嵌套经典算法

较难11
语言版本:C++Python
概述:用两层循环解决百钱百鸡、找出质数等经典数学问题,锻炼逻辑思维。

好的,让我们来一起丰富这段内容,让它变成一篇更完整、更容易理解的参考文章。我会保持原有代码和解释,同时补充更多例子、错误提醒和生活场景,让你能更轻松地掌握循环嵌套这个强大的工具。


循环嵌套的进阶用法:用两层循环解决经典数学问题

在编程中,有时候我们需要让计算机“尝试所有可能的情况”。比如,你想知道用口袋里有限的零花钱买零食,有多少种不同的买法?或者你想找出1到100之间所有的质数,用来验证一个数学猜想?这些问题如果用大脑一个个去尝试,会非常费时,但电脑却特别擅长做这种重复的“枚举”工作。这时候,循环嵌套(即一个循环里面再套一个循环)就成了我们的好帮手。今天我们就通过两个经典的数学问题,来学习如何用两层循环解决实际问题。

什么是循环嵌套?

循环嵌套就像两个互锁的齿轮:外层齿轮转一圈,内层齿轮就要转一整圈。例如,你有一个外层循环控制“班级序号”,内层循环控制“座位号”,那么外层每换一个班级,内层就会把该班级的所有座位都检查一遍。这种结构特别适合需要枚举所有组合的问题。

一、百钱百鸡:用循环找出所有购买方案

问题描述

公鸡5文钱一只,母鸡3文钱一只,小鸡3只一文钱。现在有100文钱,要买100只鸡。问公鸡、母鸡、小鸡各多少只?

解题思路

  • 公鸡最多买多少只?100文 ÷ 5文/只 = 20只。
  • 母鸡最多买多少只?100文 ÷ 3文/只 ≈ 33只。
  • 小鸡的数量 = 100 – 公鸡数 – 母鸡数(因为总鸡数是100)。

我们可以用外层循环穷举公鸡数(从0到20),内层循环穷举母鸡数(从0到33),然后根据“总钱数=100”这个条件检验小鸡的数量是否合理。注意:小鸡3只1文,所以小鸡数必须是3的倍数,并且钱数计算要准确。

代码示例(带中文注释)

#include <iostream>
using namespace std;
int main() {
    // 外层循环:穷举公鸡数量,最多20只
    for (int rooster = 0; rooster <= 20; rooster++) {
        // 内层循环:穷举母鸡数量,最多33只
        for (int hen = 0; hen <= 33; hen++) {
            // 小鸡数量 = 100 - 公鸡 - 母鸡
            int chick = 100 - rooster - hen;
            // 条件:小鸡数量不能为负,总钱数等于100文,且小鸡数是3的倍数
            if (chick >= 0 && 
                5 * rooster + 3 * hen + chick / 3 == 100 && 
                chick % 3 == 0) {
                cout << "公鸡:" << rooster 
                     << " 母鸡:" << hen 
                     << " 小鸡:" << chick << endl;
            }
        }
    }
    return 0;
}

运行结果(输出三种方案):

公鸡:0 母鸡:25 小鸡:75
公鸡:4 母鸡:18 小鸡:78
公鸡:8 母鸡:11 小鸡:81
公鸡:12 母鸡:4 小鸡:84

生活中的类比

想象你手上有100元,要买三种不同类型的文具:

  • 钢笔:5元/支
  • 铅笔:3元/支
  • 橡皮:1元/3块

你要买总共100件文具,正好花完100元。那么用循环去枚举钢笔和铅笔的数量,再算出橡皮的数量,就知道有多少种购买方式了。

常见错误提醒

  1. 小鸡数量忘记检查非负:如果公鸡和母鸡数量加起来超过100,小鸡就会变成负数,但实际不能有负数的鸡。所以 chick >= 0 是必须的。
  2. 小鸡钱数计算错误chick / 3 在C++中是整数除法,会丢失余数。因此需要单独检查 chick % 3 == 0,确保能整除。
  3. 漏掉公鸡或母鸡为0的情况:循环从0开始,这样才能枚举“不买某种鸡”的方案。

二、找出质数:用循环嵌套筛选

问题描述

找出2到100之间所有的质数。质数是指除了1和它本身以外,不能被其他正整数整除的数(比如2、3、5、7、11……)。

解题思路

  • 外层循环:遍历每个数 num,从2到100。
  • 内层循环:用 i 从2开始,检查 num 是否能被 i 整除。如果找到了一个因子(除了1和它本身),那么这个数就不是质数。
  • 为了提高效率,内层循环只需检查到 i * i <= num 即可。因为如果有一个因子大于 sqrt(num),那么它对应的另一个因子一定小于 sqrt(num),已经检查过了。这样可以减少循环次数。

代码示例(带中文注释)

#include <iostream>
using namespace std;
int main() {
    // 外层循环:检查每个数是否是质数
    for (int num = 2; num <= 100; num++) {
        // 先假设当前数是质数
        bool isPrime = true;
        // 内层循环:从2开始检查到 sqrt(num)
        for (int i = 2; i * i <= num; i++) {
            // 如果找到能整除的因子,则不是质数
            if (num % i == 0) {
                isPrime = false;
                break;   // 找到一个因子就可以提前退出内层循环
            }
        }
        // 如果 isPrime 仍然为 true,则输出这个数
        if (isPrime) {
            cout << num << " ";
        }
    }
    cout << endl;
    return 0;
}

输出结果

2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

生活中的类比

想象你是一个班长,要检查班里每个同学的作业是否“干净”(只有自己完成,没有抄袭)。对于每个同学,你需要检查他是否被其他人“影响”(即有没有除了1和自身以外的“数字朋友”)。你把每个同学和比他小的其他同学配对检查,如果发现任何两人有“共同点”(因子),就标记这个同学有问题。这样就能找出所有“干净”的同学(质数)。

常见错误提醒

  1. 忘记初始化 isPrime:如果没有写 bool isPrime = true;,那么变量的初始值是不确定的,可能导致错误判断。
  2. 内层循环范围写错:如果用 i <= num或者 i < num,会多出很多不必要的计算。正确的范围是 i * i <= num,(等价于 i <= sqrt(num))。
  3. 忘记用 break提前退出:发现一个因子后,后面的检查已经没有意义,用 break可以节省时间,尤其在数字很大时效果明显。

三、新手最容易犯的3个错误(总结)

  1. 忘记变量初始化:尤其是在内层循环中,每次外层循环开始时要重置布尔值、计数器等。比如判断质数时,如果没有在每个 num 之前设置 isPrime = true,那么上一次循环的结果会影响到这一次。
  2. 循环边界弄错:例如百钱百鸡中公鸡最大是20只,母鸡最大33只,但有人可能会写成 <= 100,导致无意义的迭代。判断质数时内层循环条件写成 i <= num 也会浪费大量时间。
  3. 整数除法与取模的混淆:百钱百鸡中小鸡数除以3要用整数除法,但必须确保能整除;如果不检查余数,可能计算出错误的总钱数。建议总是用 xiao % 3 == 0 来确认。

四、完整可运行的示例:帮小美买零食

为了让你更直观地感受循环嵌套的用途,再来看一个贴近生活的例子:

小美有20元零花钱,她想买三种零食:薯片(5元/包)、饼干(3元/包)、糖果(1元/2颗)。她一共要买10件零食(注意件数:一包薯片算1件,一包饼干算1件,2颗糖果算1件),并且正好花完20元。有多少种买法?

这里外层循环枚举薯片数量(04包,因为20÷5=4),内层循环枚举饼干数量(06包,因为20÷3≈6),然后糖果数量 = 10 – 薯片 – 饼干,再检查糖果是否非负、总钱数是否为20、糖果颗数是否为偶数(因为2颗1元,每件是2颗,所以糖果件数必须能被2整除,且每件对应2颗)。

#include <iostream>
using namespace std;
int main() {
    cout << "薯片 饼干 糖果(件) 总钱数" << endl;
    // 外层:薯片包数,最多4包
    for (int chip = 0; chip <= 4; chip++) {
        // 内层:饼干包数,最多6包
        for (int cookie = 0; cookie <= 6; cookie++) {
            // 糖果件数(注意:1件糖果=2颗,即1元)
            int candy = 10 - chip - cookie;
            // 检查件数非负、总钱数=20元、糖果件数必须非负整数
            if (candy >= 0 &&
                5 * chip + 3 * cookie + candy * 1 == 20) {
                // 糖果件数是整数,但需要确保颗数能被2整除吗?
                // 实际上,candy是件数,每件2颗,所以总颗数=2*candy,钱数= candy*1
                // 只要candy是整数就行,不需要额外检查%2,因为candy本身就是整数件。
                cout << chip << "   " << cookie << "   " 
                     << candy << "       20元" << endl;
            }
        }
    }
    return 0;
}

运行结果:

薯片 饼干 糖果(件) 总钱数
0   0   10       20元
0   5   5       20元
1   0   9       20元
2   3   5       20元
3   0   7       20元
4   1   5       20元

这个例子也展示了循环嵌套的力量:只用几行代码,就把所有可能组合都枚举出来了。

五、相关指引

如果你已经掌握了上面两个例子,还可以继续学习:

  • 多层循环:有时候需要三层甚至更多层循环,比如“百钱百鸡”的变种(买更多种家禽)。
  • 循环优化:适当缩小范围、使用 break 提前结束,可以显著提升程序效率。
  • 双重循环与 continue:在某些情况下,用 continue 可以跳过不必要的检查,让代码更简洁。
  • 枚举算法:循环嵌套是“暴力枚举”的基础,后续可以结合回溯、剪枝解决更复杂的问题(如八皇后、数独等)。

记住:计算机最擅长的就是“傻傻地”重复工作,而循环嵌套就是指挥它做这件事的最佳工具。多动手写几个小例子,你就能熟练运用它来解决有趣的数学问题了。

例题精讲

1单选题

在C++中,以下哪个循环嵌套的执行次数是m×n?

Afor(i=0;i<m;i++) for(j=0;j<n;j++)
Bfor(i=0;i<m;i++) for(j=0;j<i;j++)
Cfor(i=0;i<m;i++) for(j=0;j<n;j+=2)
Dfor(i=0;i<m;i++) for(j=0;j<m;j++)
2判断题

在循环嵌套中,break语句只能跳出最内层循环。

3填空题
以下程序使用循环嵌套打印九九乘法表(等腰三角形格式),请填空:
for(int i=1;i<=9;i++){
    for(int j=1;___;j++){
        cout<<j<<"*"<<i<<"="<<i*j<<"\t";
    }
    cout<<endl;
}
4单选题

以下关于循环嵌套的说法错误的是?

A外层循环变量变化慢,内层循环变量变化快
B循环嵌套的层数没有理论限制,但受内存限制
C内层循环的初始化和条件可以依赖于外层循环变量
D循环嵌套只能采用for语句嵌套,不能使用while嵌套
5填空题
以下程序输出100以内的所有素数,请在空白处填空:
#include <iostream>
using namespace std;
int main(){
    int i,j,isPrime;
    for(i=2;i<=100;i++){
        isPrime=1;
        for(j=2;___;j++){
            if(i%j==0){
                isPrime=0;
                break;
            }
        }
        if(isPrime) cout<<i<<" ";
    }
    return 0;
}