Appearance
第5章 算法与描述
🎯 本章学习目标
学完本章,你将能够:
- 理解算法的定义与五个基本特征(有穷性、确定性、可行性、输入、输出)
- 区分算法与程序的不同(算法必须有穷,程序可以一直运行)
- 掌握算法的三种描述方式:自然语言、流程图、伪代码
- 熟练阅读流程图:认识五种基本符号,能追踪变量变化、判断输出结果
- 掌握三种基本结构的流程图:顺序结构、分支结构、循环结构
- 理解枚举法的基本思想:确定范围 + 逐一检验
- 掌握枚举法的常见优化:减少嵌套、缩小范围、剪枝
- 理解模拟法的基本思想:按规则一步步重演过程
- 掌握模拟法的关键技巧:状态变量管理、边界条件处理
- 能够独立完成枚举和模拟类编程题,准确实现题目要求
🗺️ 本章知识图谱
算法与描述
├── 算法基础
│ ├── 算法的定义
│ ├── 五大特征
│ │ ├── 有穷性(有限步后结束)
│ │ ├── 确定性(无二义性)
│ │ ├── 可行性(每步可实现)
│ │ ├── 输入(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 /
|
( 结束 )高频考点 🔥:流程图阅读题解题三步法:
- 找入口:从"开始"框出发,顺着箭头走;
- 盯变量:准备一张草稿纸,列出所有变量,每经过一个处理框就更新变量值(这是最关键的一步!);
- 判分支:遇到菱形判断框,把当前变量值代入条件,决定走"是"还是"否";循环结构就一圈一圈走,直到条件不满足。
切忌"用眼睛看"硬猜,一定要动手逐行记录变量变化表。
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),也叫穷举法:把问题的所有可能情况逐一列举出来,逐个检验是否符合条件,符合的就是答案。
核心套路两步走:
- 确定枚举范围:答案可能是什么?把所有候选值圈出来;
- 逐一检验:对每个候选值,用题目给出的条件判断"是不是答案"。
生活类比:在一串钥匙里找能开门的那把——一把一把试,能打开的就是。
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 4073.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米右转"你就右转,不需要知道整条路为什么这么规划,照做即可。
模拟法不追求巧妙的数学公式,核心是把规则准确翻译成代码,特别适合过程性强、规则描述细致的题目。
模拟题两大注意事项 ⚠️:
- 边界条件:开始/结束的瞬间、第一天/最后一天、第一个人/最后一个人,最容易出错(差一错误 off-by-one);
- 状态更新顺序:多个变量互相影响时,必须想清楚"先更新谁、后更新谁",必要时用临时变量保存旧值,切忌用已经更新过的值参与后续计算。
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<=6 | s 变化 | i 变化 |
|---|---|---|---|
| 初始 | — | s=0 | i=1 |
| 第1圈 | 1≤6 是 | s=0+1=1 | i=2 |
| 第2圈 | 2≤6 是 | s=1+2=3 | i=3 |
| 第3圈 | 3≤6 是 | s=3+3=6 | i=4 |
| 第4圈 | 4≤6 是 | s=6+4=10 | i=5 |
| 第5圈 | 5≤6 是 | s=10+5=15 | i=6 |
| 第6圈 | 6≤6 是 | s=15+6=21 | i=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. 递推模拟:用变量滚动保存前序状态,一步步推 │
└──────────────────────────────────────────────────────┘