Skip to content

第5章 算法与描述 ​

🎯 本章学习目标 ​

学完本章,你将能够:

  1. 理解算法的定义与五个基本特征(有穷性、确定性、可行性、输入、输出)
  2. 区分算法与程序的不同(算法必须有穷,程序可以一直运行)
  3. 掌握算法的三种描述方式:自然语言、流程图、伪代码
  4. 熟练阅读流程图:认识五种基本符号,能追踪变量变化、判断输出结果
  5. 掌握三种基本结构的流程图:顺序结构、分支结构、循环结构
  6. 理解枚举法的基本思想:确定范围 + 逐一检验
  7. 掌握枚举法的常见优化:减少嵌套、缩小范围、剪枝
  8. 理解模拟法的基本思想:按规则一步步重演过程
  9. 掌握模拟法的关键技巧:状态变量管理、边界条件处理
  10. 能够独立完成枚举和模拟类编程题,准确实现题目要求

🗺️ 本章知识图谱 ​

算法与描述
├── 算法基础
│   ├── 算法的定义
│   ├── 五大特征
│   │   ├── 有穷性(有限步后结束)
│   │   ├── 确定性(无二义性)
│   │   ├── 可行性(每步可实现)
│   │   ├── 输入(0个或多个)
│   │   └── 输出(至少1个)
│   └── 算法 vs 程序
├── 三种描述方式
│   ├── 自然语言
│   │   ├── 优点:通俗易懂
│   │   └── 缺点:有二义性
│   ├── 流程图 ⭐
│   │   ├── 起止框(椭圆)
│   │   ├── 处理框(矩形)
│   │   ├── 判断框(菱形,一进两出)
│   │   ├── 输入输出框(平行四边形)
│   │   └── 流程线(箭头)
│   └── 伪代码
│       ├── 赋值:← 或 :=
│       ├── 分支:if...then...else
│       └── 循环:while / for
├── 三种基本结构
│   ├── 顺序结构
│   ├── 分支结构(选择结构)
│   └── 循环结构
│       ├── 当型循环(先判断后执行:while)
│       └── 直到型循环(先执行后判断:do-while)
├── 枚举法(穷举法)⭐
│   ├── 核心思想:范围 + 条件
│   ├── 适用条件:范围有限、可判定
│   ├── 经典例题
│   │   ├── 水仙花数
│   │   ├── 百钱百鸡
│   │   ├── 素数判定
│   │   └── 鸡兔同笼
│   └── 优化方法
│       ├── 减少嵌套层数
│       ├── 缩小枚举范围
│       └── 剪枝(提前退出)
└── 模拟法 ⭐
    ├── 核心思想:按规则一步步演
    ├── 关键:准确翻译规则
    ├── 注意事项
    │   ├── 边界条件(首尾、第一天/最后一天)
    │   └── 状态更新顺序
    └── 经典例题
        ├── 约瑟夫问题(报数出圈)
        ├── 日期推算
        └── 投票统计

一、什么是算法 ​

1.1 算法的定义 ​

算法(Algorithm) 是解决某一类问题的、明确而有限的步骤序列。简单说:为了完成一个任务,你"先做什么、再做什么"的完整操作清单。

我们每天都在使用算法:菜谱是做菜的算法,说明书是组装家具的算法,"查字典先找部首再找页码"也是算法。

程序 = 数据结构 + 算法。数据结构是"原料怎么摆放",算法是"步骤怎么执行"。

1.2 算法的五个特征 ⭐⭐⭐ ​

一个方法要能称为"算法",必须满足五个特征:

特征含义生活类比反例
有穷性算法必须在执行有限步骤后结束,每步耗时有限菜谱总有最后一步"一直翻炒直到世界末日" ❌
确定性每条指令含义明确,无二义性,相同输入必得相同输出"加盐3克""加少许盐"(少许是多少?)❌
可行性每一步都能通过已实现的基本操作有限次完成用现有厨具能做出来"把牛肉瞬间变成牛排" ❌
输入有 0 个或多个输入(可以没有输入)做菜的食材—
输出有 1 个或多个输出(没有输出的算法毫无意义)做好的菜忙活半天什么都没产出 ❌

高频考点 🔥: 五个特征的细节辨析——

  • 输入可以是 0 个(如输出固定内容的程序),但输出至少 1 个!
  • "有穷性"强调步骤有限;"确定性"强调每步无歧义。两者常被张冠李戴,考试爱考判断题。
  • 注意区分"算法"和"程序":程序可以不满足有穷性(如操作系统一直运行),但算法必须有穷。

易错点 ⚠️: "算法必须有输入"是错误说法!算法可以有零个输入。但"算法可以没有输出"也是错的——输出至少一个。


二、算法的三种描述方式 ​

同一个算法,可以用三种方式描述:自然语言、流程图、伪代码。三级考试中,流程图阅读题是常客,务必掌握。

2.1 自然语言描述 ​

用中文(或英文)加上数学语言,把步骤一条条写出来。

示例:判断一个正整数 n 是否为素数

第1步:输入正整数 n。 第2步:若 n 等于 1,输出"不是素数",结束。 第3步:令 i = 2。 第4步:若 i 能整除 n,输出"不是素数",结束。 第5步:令 i 增加 1;若 i ≤ n-1,回到第4步。 第6步:输出"是素数",结束。

优点缺点
通俗易懂,不需要任何编程基础文字冗长,不够直观
便于与人交流思路容易产生二义性("回到上面"到底回到哪步?)
—描述分支、循环等结构时很啰嗦

易错点 ⚠️: 自然语言描述的最大缺点就是不严谨、有二义性,这正是流程图和伪代码要解决的问题。

2.2 流程图描述 ⭐⭐⭐⭐⭐ ​

流程图(Flow Chart)用规定的图形符号 + 流程线来表示算法,直观清晰,是三级必考内容。

(1)五种基本符号 ​

符号名称形状画法(ASCII示意)作用
起止框圆角矩形(两端圆弧)( 开始 )表示算法的开始和结束,每个流程图恰好一个开始、至少一个结束
处理框矩形[ x ← x+1 ]表示赋值、计算等处理操作,一个入口一个出口
判断框菱形< x > 0? >表示条件判断,一个入口、两个(或多个)出口,出口标注"是/否"(Y/N)
输入输出框平行四边形/ 输入 n /表示输入数据或输出结果
流程线带箭头的线↓ → ↑ ←表示执行方向,箭头指出下一步走向
        (  开始  )          ← 起止框:圆角矩形
            |
        / 输入 n /          ← 输入输出框:平行四边形
            |
        [ i ← 2 ]           ← 处理框:矩形
            |
        < n % i == 0 ? >    ← 判断框:菱形
          /       \
       是/         \否
        |           |
   /输出"否"/   [ i ← i+1 ]
        |           |
     ( 结束 )   < i <= n-1 ? >
                  /      \
               是/        \否
                |          |
            (回到判断)  /输出"是"/
                          |
                       ( 结束 )

高频考点 🔥: 各框的"出入口数量"——

  • 处理框、输入输出框、起止框:一进一出
  • 判断框:一进两出(是/否两个分支) 考试常给四个图形问"哪个是判断框",记住菱形 = 判断。

(2)三种基本结构的流程图 ​

任何复杂算法都由三种基本结构组成:

① 顺序结构:一步接一步,直线执行。

   ( 开始 )
      |
  [ a ← 2 ]
      |
  [ b ← 3 ]
      |
  [ c ← a+b ]
      |
  / 输出 c /
      |
   ( 结束 )

② 分支结构(选择结构):根据条件决定走哪条路。

      < x >= 60 ? >
       /        \
    是/          \否
     |            |
 /输出"及格"/  /输出"不及格"/
     \            /
      \          /
       ( 结束 )

③ 循环结构:重复执行某些步骤,分两种——

  • 当型循环(先判断后执行):条件满足才执行循环体,对应 while。
      ( 开始 )
         |
   +---->+ < s <= 100 ? >---否---> / 输出 sum / --->(结束)
   |        |是
   |    [ sum ← sum+s ]
   |        |
   |    [ s ← s+1 ]
   +--------+
   (流程线绕回判断框)
  • 直到型循环(先执行后判断):先执行循环体,再判断是否继续,对应 do-while。
   ( 开始 )
      |
  [ s ← 1, sum ← 0 ]
      |
  +--[ sum ← sum+s ]
  |   [ s ← s+1 ]
  |      |
  +---< s <= 100 ? >--是--+(回到处理框)
         |否
    / 输出 sum /
         |
      ( 结束 )

高频考点 🔥:流程图阅读题解题三步法:

  1. 找入口:从"开始"框出发,顺着箭头走;
  2. 盯变量:准备一张草稿纸,列出所有变量,每经过一个处理框就更新变量值(这是最关键的一步!);
  3. 判分支:遇到菱形判断框,把当前变量值代入条件,决定走"是"还是"否";循环结构就一圈一圈走,直到条件不满足。

切忌"用眼睛看"硬猜,一定要动手逐行记录变量变化表。

2.3 伪代码描述 ​

伪代码(Pseudocode)介于自然语言和真实代码之间:用类似程序的结构(赋值、if、while、for、缩进)写步骤,但不拘泥于某种语言的语法,重点是表达算法逻辑。

常见伪代码符号约定:

符号/关键字含义
← 或 :=赋值(把右边的值给左边变量)
=判断相等
> < ≥ ≤ ≠比较运算
and / or / not逻辑与/或/非
if ... then ... else ...分支
while ... do ...当型循环
for i ← 1 to n do计数循环
mod取余
div整除
print / read输出 / 输入
// 或大括号注释/语句块

示例:判断素数的伪代码

算法:IsPrime(n)
    if n = 1 then
        print "不是素数"
        return
    for i ← 2 to ⌊√n⌋ do
        if n mod i = 0 then
            print "不是素数"
            return
    print "是素数"
优点缺点
结构清晰,贴近代码,方便直接翻译成程序仍需掌握一定的"代码感"
简洁、无语法负担,专注逻辑没有统一标准,不同书写法略有差异
方便描述分支和循环—

三种描述方式对比小结: 自然语言最通俗但有二义性;流程图最直观、考试最爱考阅读;伪代码最接近程序、最方便上机实现。


三、枚举法(穷举法)⭐⭐⭐⭐ ​

3.1 基本思想 ​

枚举法(Enumeration),也叫穷举法:把问题的所有可能情况逐一列举出来,逐个检验是否符合条件,符合的就是答案。

核心套路两步走:

  1. 确定枚举范围:答案可能是什么?把所有候选值圈出来;
  2. 逐一检验:对每个候选值,用题目给出的条件判断"是不是答案"。

生活类比:在一串钥匙里找能开门的那把——一把一把试,能打开的就是。

3.2 适用条件 ​

枚举法不是万能的,能用枚举要满足:

  • 范围有限:候选情况的数量是有限的、可列举的(无限个不行);
  • 可判定:对每个候选情况,能明确判断它是否满足条件;
  • 规模可接受:候选数量在计算机算力范围内(三级考试数据范围都很小,基本不用担心超时)。

枚举法通常用循环结构实现:单条件用一层循环,多变量用多层嵌套循环。

易错点 ⚠️: 枚举最容易犯两个错误——① 范围定错(漏情况或多情况);② 条件写错(判断语句和题目要求不一致)。写完代码务必用样例手算验证。

3.3 例题1:水仙花数 ​

题目: 输出所有的"水仙花数"。水仙花数是指一个三位数,其各位数字的立方和等于它本身。例如 153 = 1³ + 5³ + 3³。

思路: 枚举范围明确——所有三位数 100~999,共 900 个数。对每个数拆出个位、十位、百位,判断立方和是否等于自身。

cpp
#include <iostream>
using namespace std;

int main() {
    // 枚举所有三位数
    for (int n = 100; n <= 999; n++) {
        int a = n / 100;        // 百位:整除100
        int b = n / 10 % 10;    // 十位:先整除10再对10取余
        int c = n % 10;         // 个位:对10取余
        // 判断各位立方和是否等于自身
        if (a*a*a + b*b*b + c*c*c == n) {
            cout << n << " ";
        }
    }
    cout << endl;
    return 0;
}
// 输出:153 370 371 407

3.4 例题2:百钱百鸡(多重枚举 + 优化) ​

题目: 公鸡每只 5 元,母鸡每只 3 元,小鸡 3 只 1 元。用 100 元买 100 只鸡,问公鸡、母鸡、小鸡各买多少只?列出所有方案。

思路: 设公鸡 x 只、母鸡 y 只、小鸡 z 只,满足:

  • x + y + z = 100(共100只)
  • 5x + 3y + z/3 = 100(共100元)

最直接的想法是三重循环枚举 x、y、z。但注意:z = 100 - x - y 可以直接算出来,不需要枚举!这就是减少嵌套的优化思路。

cpp
#include <iostream>
using namespace std;

int main() {
    // 公鸡最多 100/5=20 只;母鸡最多 100/3≈33 只
    for (int x = 0; x <= 20; x++) {
        for (int y = 0; y <= 33; y++) {
            int z = 100 - x - y;           // 小鸡数量直接由总数推出,无需第三重循环
            if (z >= 0 && z % 3 == 0       // 小鸡数必须非负且是3的倍数(1元3只)
                && 5*x + 3*y + z/3 == 100) {
                cout << "公鸡" << x << " 母鸡" << y << " 小鸡" << z << endl;
            }
        }
    }
    return 0;
}
// 输出:
// 公鸡0 母鸡25 小鸡75
// 公鸡4 母鸡18 小鸡78
// 公鸡8 母鸡11 小鸡81
// 公鸡12 母鸡4 小鸡84

枚举优化入门 🔥:

  • 减少嵌套层数:能由方程直接算出的变量就不要枚举(百钱百鸡:三重循环 → 双重循环);
  • 缩小枚举范围:公鸡最多 20 只而不是 100 只,循环上界按预算收紧;
  • 剪枝思想:在枚举过程中,一旦发现某分支不可能满足条件,就提前跳过(如 z 为负数立即 continue)。三级阶段建立这个意识即可,不必追求极致。

3.5 例题3:判断素数 & 找区间内素数 ​

题目: 输入一个正整数 n(n≥2),判断它是不是素数。素数:大于 1,且只能被 1 和自身整除的数。

思路: 枚举"除数" i 从 2 到 n-1,只要有一个能整除 n,n 就不是素数。优化:只需枚举到 √n(因为若 n = a×b,则 a、b 中至少有一个 ≤ √n)。

cpp
#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;                          // 输入待判断的数

    bool isPrime = true;               // 假设是素数
    //n=2 时循环一次都不走,直接判定为素数
    for (int i = 2; i * i <= n; i++) { // 只枚举到 sqrt(n):i*i<=n
        if (n % i == 0) {              // 能被整除 → 出现因数
            isPrime = false;
            break;                     // 剪枝:已确定不是素数,立即退出
        }
    }

    if (isPrime) cout << n << " 是素数" << endl;
    else         cout << n << " 不是素数" << endl;
    return 0;
}

高频考点 🔥: 素数判定的边界——

  • 1 不是素数(也不是合数);2 是最小的素数,也是唯一的偶素数;
  • 循环上界 i*i <= n 等价于 i <= √n,注意不要写成 i*i < n(漏掉 i=√n 的情况);
  • break 的作用就是"剪枝":找到一个因数即可下结论。

四、模拟法 ⭐⭐⭐⭐ ​

4.1 基本思想 ​

模拟法(Simulation):按照题目描述的规则,一步一步、老老实实地把过程"演"一遍,让程序完全照着规则走,最终自然得到结果。

生活类比:跟着导航走——它说"前方500米右转"你就右转,不需要知道整条路为什么这么规划,照做即可。

模拟法不追求巧妙的数学公式,核心是把规则准确翻译成代码,特别适合过程性强、规则描述细致的题目。

模拟题两大注意事项 ⚠️:

  1. 边界条件:开始/结束的瞬间、第一天/最后一天、第一个人/最后一个人,最容易出错(差一错误 off-by-one);
  2. 状态更新顺序:多个变量互相影响时,必须想清楚"先更新谁、后更新谁",必要时用临时变量保存旧值,切忌用已经更新过的值参与后续计算。

4.2 例题1:约瑟夫问题(报数出圈模拟) ​

题目: n 个人围成一圈,从第 1 个人开始从 1 报数,报到 m 的人出圈,下一个人重新从 1 开始报数。求最后剩下的人的编号。

思路(纯模拟): 用一个布尔数组标记每个人是否还在圈中。用一个计数器模拟报数,每数到 m 就把当前人标记为出圈,直到只剩一人。

cpp
#include <iostream>
using namespace std;

int main() {
    int n, m;
    cin >> n >> m;                     // n个人,数到m出圈

    bool inCircle[1005];               // inCircle[i]:编号i的人是否在圈中
    for (int i = 1; i <= n; i++) inCircle[i] = true;

    int alive = n;                     // 圈中剩余人数
    int pos = 0;                       // 当前位置(0表示还没开始)
    while (alive > 1) {                // 剩1人时结束
        int count = 0;                 // 本轮报数计数器
        while (count < m) {
            pos++;                     // 走到下一个人
            if (pos > n) pos = 1;      // 围成一圈:走到n后回到1
            if (inCircle[pos]) count++;// 在圈中的人才报数
        }
        inCircle[pos] = false;         // 报数到m的人出圈
        alive--;
    }

    for (int i = 1; i <= n; i++) {
        if (inCircle[i]) cout << "最后剩下:" << i << "号" << endl;
    }
    return 0;
}
// 输入:10 3
// 输出:最后剩下:4号

易错点 ⚠️: 约瑟夫模拟中最容易错的两点——① 出圈的人不报数,所以计数器只对在圈中的人递增;② "围成一圈"靠 pos > n 时回到 1 实现,漏掉这步数组就越界了。

4.3 例题2:日期计算(今天之后第 k 天是几月几日) ​

题目: 给定年月日和整数 k,输出这一天之后 k 天的日期(假设都是平年)。

思路: 完全模拟日历翻动——一天一天往后推:日期 day+1,若超过本月天数就"月份+1、日期归1";月份超过12就"年份+1、月份归1"。

cpp
#include <iostream>
using namespace std;

int main() {
    int year, month, day, k;
    cin >> year >> month >> day >> k;

    // 平年各月天数,下标0占位不用
    int days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

    for (int i = 1; i <= k; i++) {    // 一天一天模拟,共推k天
        day++;                        // 日期加1
        if (day > days[month]) {      // 超过本月天数 → 进入下月
            day = 1;
            month++;
            if (month > 12) {         // 超过12月 → 进入下一年
                month = 1;
                year++;
                // 若考虑闰年,2月天数在此更新;本题按平年处理
            }
        }
    }

    cout << year << "-" << month << "-" << day << endl;
    return 0;
}
// 输入:2023 2 27 3
// 输出:2023-3-2

易错点 ⚠️: 日期模拟的经典坑——先加日,再判断月,最后判断年,顺序不能乱。若题目涉及闰年,记住口诀"四年一闰,百年不闰,四百年再闰":(y%4==0 && y%100!=0) || y%400==0。

4.4 例题3:排队接水 / 计数器模拟(投票统计) ​

题目: 一次投票有 3 位候选人(编号 1、2、3),输入 n 张选票上的编号(0 表示弃权/无效票结束输入),统计每人得票数和最高得票者。

思路: 准备计数器数组,读一张票就给对应候选人计数+1,全程"照章办事",最后比较计数器。

cpp
#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;                          // 选票总数

    int cnt[4] = {0};                  // cnt[1]~cnt[3] 分别统计三位候选人
    for (int i = 1; i <= n; i++) {
        int vote;
        cin >> vote;
        if (vote >= 1 && vote <= 3) {  // 有效票才计数
            cnt[vote]++;
        }
        // vote为0或其他值视为无效票,直接忽略
    }

    for (int i = 1; i <= 3; i++) {
        cout << i << "号候选人:" << cnt[i] << "票" << endl;
    }

    // 找得票最高者
    int best = 1;
    for (int i = 2; i <= 3; i++) {
        if (cnt[i] > cnt[best]) best = i;
    }
    cout << "得票最高:" << best << "号候选人" << endl;
    return 0;
}
// 输入:
// 8
// 1 2 1 3 1 2 0 1
// 输出:
// 1号候选人:4票
// 2号候选人:2票
// 3号候选人:1票
// 得票最高:1号候选人

模拟法小结: 计数器累加、开关门状态翻转、骰子点数统计、扑克牌发牌……这类题目的共同点是"规则直白、过程繁琐"。解法没有捷径,就是选好变量记录状态 → 按规则逐轮更新 → 最后读取结果。


五、枚举法 vs 模拟法:怎么选? ​

对比项枚举法(穷举)模拟法
核心思想列出所有候选,逐个检验条件照题目规则一步步重演过程
关键动作确定范围 + 条件判断记录状态 + 按规则更新
程序结构以循环 + if 判断为主以状态变量 + 顺序/循环更新为主
典型题目水仙花数、百钱百鸡、找素数、两数之和约瑟夫问题、日期推算、投票统计、骰子游戏
易错点范围遗漏、条件写错边界条件、状态更新顺序
优化方向减少嵌套、缩小范围、剪枝理清规则、注意边界

高频考点 🔥: 两种方法经常结合使用——例如"模拟每一天的天气,并枚举所有可能的播种日期"。判断题干是让你"找满足条件的对象"(枚举)还是"重演一个过程"(模拟),就能快速定方向。


六、📝 实战练习 ​

练习题 1(程序阅读题) ​

题目: 阅读下面的流程图,当输入 n = 6 时,输出的结果是( )

        ( 开始 )
           |
       / 输入 n /
           |
      [ s ← 0, i ← 1 ]
           |
   +------>+
   |   < i <= n ? >---否---> / 输出 s / --->(结束)
   |       |是
   |  [ s ← s + i ]
   |  [ i ← i + 1 ]
   +-------+

A. 15  B. 21  C. 6  D. 28

🔑 点击查看答案与解析

答案: B

解析: 列变量变化表,逐圈跟踪:

循环圈数判断 i<=6s 变化i 变化
初始—s=0i=1
第1圈1≤6 是s=0+1=1i=2
第2圈2≤6 是s=1+2=3i=3
第3圈3≤6 是s=3+3=6i=4
第4圈4≤6 是s=6+4=10i=5
第5圈5≤6 是s=10+5=15i=6
第6圈6≤6 是s=15+6=21i=7
第7次判断7≤6 否退出循环,输出 s=21—

即 s = 1+2+…+6 = 21,选 B。

流程图阅读口诀: 菱形做选择,矩形做计算,平行四边形管输入输出;变量变化列表格,一圈一圈填到黑。


练习题 2(编程题) ​

题目: "鸡兔同笼":笼中有鸡和兔共 30 只,脚共 90 只。求鸡和兔各有多少只?(请用枚举法编程求解)

🔑 点击查看答案与解析

思路: 枚举鸡的数量 x(0~30),兔的数量 = 30 - x 直接推出;判断脚数 2x + 4(30-x) 是否等于 90。

参考答案(C++11):

cpp
#include <iostream>
using namespace std;

int main() {
    for (int x = 0; x <= 30; x++) {       // x:鸡的数量
        int y = 30 - x;                   // 兔的数量
        if (2 * x + 4 * y == 90) {        // 脚共90只
            cout << "鸡" << x << "只,兔" << y << "只" << endl;
        }
    }
    return 0;
}
// 输出:鸡15只,兔15只

数学验证: 设鸡 x 兔 y,x+y=30,2x+4y=90,解得 x=15,y=15,与枚举结果一致 ✅。


练习题 3(编程题) ​

题目: 有一堆豆子,第 1 天取出 1 颗,第 2 天取出 2 颗,第 3 天取出 3 颗……每天比前一天多取 1 颗。若豆子总数为 n 颗,问第几天结束后豆子第一次被取完或不够取?输出该天结束时剩余的豆子数(若正好取完输出 0)。

输入: 一个整数 n(1 ≤ n ≤ 10000)。

🔑 点击查看答案与解析

思路: 纯模拟——每天按"天数"取豆子,更新剩余数量,当剩余豆子不够当天取时停止。注意边界:豆子够取才取,不够取就停止并输出当前剩余。

参考答案(C++11):

cpp
#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;                       // 豆子总数

    int day = 1;                    // 当天应取的数量 = 天数
    while (n >= day) {              // 豆子还够当天取
        n = n - day;                // 取出day颗(状态更新:先判断够不够,再取)
        day++;                      // 下一天取day+1颗
    }
    // 退出循环时:n < day,豆子已不够当天取
    cout << n << endl;              // 剩余豆子数
    return 0;
}
// 输入:10 → 取1剩9、取2剩7、取3剩4、第4天该取4但剩4,够取→剩0,第5天该取5不够
//       输出:0
// 输入:8  → 取1剩7、取2剩5、取3剩2、第4天该取4但只剩2,不够 → 输出:2

解析要点: 循环条件 n >= day 是边界关键——"够取"才进入循环取豆子。n=10 时第4天取完最后4颗剩0;n=8 时第3天后剩2,第4天应取4但不够,停止,输出2。


练习题 4(单选题) ​

题目: 下列关于算法的说法,正确的有( )

① 算法必须有一个或多个输入 ② 算法必须有一个或多个输出 ③ 算法的每一步必须有明确含义,不能有二义性,这是算法的确定性 ④ 流程图中,菱形框表示处理操作 ⑤ 用自然语言描述算法的优点是直观严谨、没有二义性

A. ②③  B. ①②③  C. ②③④  D. ③④⑤

🔑 点击查看答案与解析

答案: A

解析:

  • ① 错误 ❌:算法可以有 0 个输入(零输入合法)。
  • ② 正确 ✅:输出至少 1 个。
  • ③ 正确 ✅:无二义性正是确定性的含义。
  • ④ 错误 ❌:菱形是判断框,不是处理框。处理框是矩形。
  • ⑤ 错误 ❌:自然语言通俗易懂但有二义性,"严谨无二义性"说反了。

选 A(②③)。


练习题 5(单选题) ​

题目: 下列关于流程图符号的说法,正确的是( )

① 起止框用椭圆表示,一个流程图可以有多个开始框 ② 处理框用矩形表示,有一个入口一个出口 ③ 判断框用菱形表示,有两个入口一个出口 ④ 输入输出框用平行四边形表示 ⑤ 流程线用带箭头的线表示

A. ①②③  B. ②④⑤  C. ②③④  D. ①④⑤

🔑 点击查看答案与解析

答案: B

解析:

  • ① 错误 ❌:起止框用椭圆表示正确,但一个流程图恰好一个开始框(可以有多个结束框)。
  • ② 正确 ✅:处理框(矩形)是一进一出,对应顺序执行。
  • ③ 错误 ❌:判断框(菱形)是一进两出(是/否两个分支),不是两进一出。
  • ④ 正确 ✅:输入输出框用平行四边形表示。
  • ⑤ 正确 ✅:流程线用带箭头的线,表示执行方向。

正确的有 ②④⑤,选 B。


练习题 6(程序阅读题) ​

题目: 阅读以下 C++ 程序,输出的数的个数是( )

cpp
#include <iostream>
using namespace std;

int main() {
    int cnt = 0;
    for (int n = 100; n <= 999; n++) {
        int a = n / 100;
        int b = n / 10 % 10;
        int c = n % 10;
        if (a*a*a + b*b*b + c*c*c == n) {
            cnt++;
        }
    }
    cout << cnt << endl;
    return 0;
}

A. 3  B. 4  C. 5  D. 6

🔑 点击查看答案与解析

答案: B

解析: 程序的功能是统计三位水仙花数的个数。水仙花数是指一个三位数,其各位数字的立方和等于该数本身。

三位水仙花数共有 4 个:

  • 153 = 1³ + 5³ + 3³ = 1 + 125 + 27 = 153 ✓
  • 370 = 3³ + 7³ + 0³ = 27 + 343 + 0 = 370 ✓
  • 371 = 3³ + 7³ + 1³ = 27 + 343 + 1 = 371 ✓
  • 407 = 4³ + 0³ + 7³ = 64 + 0 + 343 = 407 ✓

所以 cnt = 4,选 B。

💡 记忆口诀:三位水仙共四朵,153、370、371、407。


练习题 7(编程题) ​

题目: 斐波那契数列的前两项是 1、1,从第三项开始每一项等于前两项之和:1, 1, 2, 3, 5, 8, 13, ... 输入 n(n ≤ 40),输出第 n 项。

输入样例:

7

输出样例:

13
🔑 点击查看答案与解析

参考代码(C++):

cpp
#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;

    if (n == 1 || n == 2) {
        cout << 1 << endl;
        return 0;
    }

    int a = 1, b = 1;   // a:第n-2项,b:第n-1项
    int c;              // c:第n项
    for (int i = 3; i <= n; i++) {
        c = a + b;      // 第i项 = 第i-2项 + 第i-1项
        a = b;          // 更新:下一轮的 n-2 变成原来的 n-1
        b = c;          // 更新:下一轮的 n-1 变成原来的 n
    }

    cout << b << endl;
    return 0;
}

解析:

这是一道典型的递推模拟题——用两个变量滚动保存前两项,一步步推算出第 n 项。

  • 初始:第 1 项 = 1,第 2 项 = 1
  • 第 3 项 = 第 1 项 + 第 2 项 = 1 + 1 = 2
  • 第 4 项 = 第 2 项 + 第 3 项 = 1 + 2 = 3
  • ...以此类推

验证(n=7):1, 1, 2, 3, 5, 8, 13 → 第 7 项是 13 ✓

🍎 生活比喻:斐波那契数列就像兔子繁殖——每对兔子每个月生一对小兔子,新生的兔子第二个月开始繁殖。数量增长正好符合"前两项之和"的规律。

⚠️ 易错提醒:n=1 和 n=2 都是 1,不要从 0 开始算。循环从 i=3 开始到 i=n,共执行 n-2 次。


练习题 8(编程题) ​

题目: 输出 1~100 中所有的完全平方数(一个整数是另一个整数的平方,如 4=2²、9=3²),以及它们的个数。

输出样例:

1 4 9 16 25 36 49 64 81 100
共10个
🔑 点击查看答案与解析

参考代码(C++,方法一:枚举平方数):

cpp
#include <iostream>
using namespace std;

int main() {
    int cnt = 0;
    // 枚举 i,i*i 就是完全平方数
    for (int i = 1; i * i <= 100; i++) {
        cout << i * i << " ";
        cnt++;
    }
    cout << endl;
    cout << "共" << cnt << "个" << endl;
    return 0;
}

参考代码(方法二:枚举每个数,判断是不是完全平方数):

cpp
#include <iostream>
#include <cmath>
using namespace std;

int main() {
    int cnt = 0;
    for (int n = 1; n <= 100; n++) {
        int root = (int)sqrt(n);   // 取平方根的整数部分
        if (root * root == n) {    // 验证:平方后是否等于原数
            cout << n << " ";
            cnt++;
        }
    }
    cout << endl;
    cout << "共" << cnt << "个" << endl;
    return 0;
}

解析:

  • 方法一更高效:直接枚举平方根 i,i 从 1 到 10(因为 10²=100),输出 i*i 即可。只需循环 10 次。
  • 方法二更直观:遍历 1~100 每个数,用 sqrt 求平方根后验证。需要循环 100 次,但思路简单。

验证:1²=1, 2²=4, 3²=9, ..., 10²=100,共 10 个完全平方数 ✓

💡 两种方法体现了枚举的两种思路:

  • 方法一:枚举"答案的一部分"(平方根),再构造出完整答案
  • 方法二:枚举所有"候选",逐一检验是否符合条件 方法一效率更高,但需要动脑子想到"枚举平方根"这个角度。

七、📊 考点总结表 ​

考点重要程度出题形式易错点
算法五个特征(有穷/确定/可行/输入/输出)⭐⭐⭐选择/判断输入0个还是1个记混;有穷性和确定性张冠李戴
算法 vs 程序(算法必须有穷)⭐⭐⭐判断以为程序也一定有穷
流程图五种符号识别⭐⭐⭐⭐选择/填空判断框出入口数搞反;平行四边形功能记错
流程图阅读(变量追踪、循环次数)⭐⭐⭐⭐⭐流程图阅读大题循环条件边界错误;少算一圈或多算一圈
三种基本结构(顺序/分支/循环)⭐⭐⭐⭐选择/判断当型循环和直到型循环分不清
伪代码符号(←、mod、if/while/for)⭐⭐⭐程序阅读赋值符号和等号搞混
枚举法思想(范围+条件)⭐⭐⭐⭐编程/程序填空枚举范围定错(漏情况/多情况);条件写错
枚举优化(减嵌套、剪枝、缩范围)⭐⭐⭐编程题不知道如何优化;三重循环能解决的非要写五重
模拟法思想(状态更新、边界条件)⭐⭐⭐⭐编程大题边界差一错误;状态更新顺序搞反
经典题型:水仙花数、素数、鸡兔同笼⭐⭐⭐⭐编程/填空数字拆分错误;素数边界(1不是素数、2是素数)
经典题型:约瑟夫问题、日期推算⭐⭐⭐⭐编程大题围成一圈的回绕处理;月份天数记错

⭐ 必背速记卡 ​

┌──────────────────────────────────────────────────────┐
│              ⭐ 算法与描述必背速记卡                     │
├──────────────────────────────────────────────────────┤
│  1. 算法五特征:有穷性、确定性、可行性、输入、输出       │
│  2. 输入可以0个,输出至少1个                           │
│  3. 算法必须有穷;程序可以无限运行(如操作系统)         │
│  4. 流程图:椭圆起止、矩形处理、菱形判断、平行四边形IO  │
│  5. 判断框:一进两出(是/否两个分支)                   │
│  6. 当型循环:先判断后执行(while)                    │
│  7. 直到型循环:先执行后判断(do-while)               │
│  8. 流程图阅读三步法:找入口、盯变量、判分支            │
│  9. 枚举法两步走:确定范围 + 逐一检验条件               │
│ 10. 枚举优化三招:减嵌套、缩范围、剪枝(提前退出)      │
│ 11. 模拟法核心:准确翻译规则,一步一步演               │
│ 12. 模拟两大坑:边界差一错误、状态更新顺序              │
│ 13. 素数判定:从2到√n,有因数就不是素数                │
│ 14. 水仙花数:三位数,各位立方和等于自身(共4个)       │
│ 15. 递推模拟:用变量滚动保存前序状态,一步步推          │
└──────────────────────────────────────────────────────┘

百炼成钢,融会贯通