算法入门期 Week1 周末教案:函数与递归
一、周六上午:函数与递归精讲(3小时)
1. 开场与回顾(9:00–9:30)
环节 | 内容 | 形式与互动 |
|---|
上周错题复盘 | 快速回顾语法基础期 Week4 的高频错题:- 数组越界- 字符串\0结束符- 循环条件错误 | 投影展示2–3个典型错误代码,让学生“找茬”,并说明错误原因。 |
引入函数 | 提出问题:“如果一段计算面积的代码要在10个地方用,怎么办?”引出“函数”的核心价值:代码复用、模块化、易维护。 | 学生自由发言,老师总结:把重复代码封装成函数,需要时直接调用。 |
今日目标 | 1. 掌握函数定义与调用;2. 理解递归思想;3. 能写简单递归函数。 | 板书“今日目标”,让学生明确学习方向。 |
2. 函数基础:从“重复代码”到“函数封装”(9:30–10:30)
环节 | 内容 | 形式与互动 |
|---|
函数三要素 | 1. 返回值类型:函数执行后返回什么类型的数据(如int、void);2. 函数名:见名知意(如calcArea);3. 参数列表:函数需要什么输入(如int r)。 | 用“点外卖”比喻:- 返回值=外卖(你要的东西);- 函数名=餐厅名(去哪取);- 参数=订单(告诉餐厅要什么)。 |
函数定义与调用 | 演示:定义一个“计算圆面积”的函数,并在main中调用。 | 板书代码:cpp<br>double calcArea(double r) {<br> return 3.14 * r * r;<br>}<br>int main() {<br> double s = calcArea(5);<br> cout << s << endl;<br> return 0;<br>}<br>强调:return的作用是将结果“交还”给调用者。 |
参数传递 | 对比:- 值传递:形参是实参的“副本”,修改形参不影响实参(如swap1函数);- 地址传递:形参是实参的“地址”,修改形参会影响实参(如swap2函数,用指针实现)。 | 现场写两段代码:1. 值传递交换两个数(失败案例);2. 地址传递交换两个数(成功案例)。让学生观察输出差异,理解“副本”和“原地址”的区别。 |
课堂小练习 | 编写一个函数int max(int a, int b),返回两个数中的较大值。 | 学生独立写代码,老师巡视,选取2名学生的代码投影展示,点评优劣(如是否处理a==b的情况)。 |
3. 递归思想:自己调用自己(10:40–12:00)
环节 | 内容 | 形式与互动 |
|---|
什么是递归 | 定义:函数直接或间接调用自身。核心思想:把大问题拆成小问题,小问题和大问题解法相同。 | 用“俄罗斯套娃”比喻:打开一个大娃娃,里面有一个小一号的娃娃,小娃娃里面还有更小的……直到最小那个(终止条件)。 |
递归三要素 | 1. 终止条件:什么时候停止递归(避免死循环);2. 递推关系:大问题如何拆成小问题(如f(n) = f(n-1) + ...);3. 返回处理:子问题的结果如何合并为大问题的结果。 | 板书“递归三要素”,用“剥洋葱”比喻:- 终止条件=剥到最里面那层(不能再剥了);- 递推关系=每次剥掉一层;- 返回处理=把每层的结果“合起来”。 |
经典例题1:阶乘 | 问题:求n!(n! = 1×2×...×n,0! = 1)。分析:- 终止条件:n <= 1时,返回1;- 递推关系:n! = n × (n-1)!;- 代码实现。 | 板书代码:cpp<br>int factorial(int n) {<br> if (n <= 1) return 1; // 终止条件<br> return n * factorial(n - 1); // 递推关系+返回处理<br>}<br>用“调用栈”图示演示factorial(3)的执行过程:factorial(3) → 3*factorial(2) → 3*(2*factorial(1)) → 3*(2 * 1) = 6。 |
经典例题2:斐波那契数列 | 问题:求第n个斐波那契数(F(1)=1, F(2)=1, F(n)=F(n-1)+F(n-2))。分析:- 终止条件:`n == 1 | |
课堂小练习 | 编写递归函数int gcd(int a, int b),用辗转相除法求最大公约数。 | 学生独立写代码,老师提示:- 终止条件:b == 0时,返回a;- 递推关系:gcd(a, b) = gcd(b, a % b)。 |
二、周六下午:实战演练 + 代码评审(3小时)
1. 综合项目:“汉诺塔”思想实践(14:00–15:30)
环节 | 内容 | 形式与互动 |
|---|
问题引入 | 汉诺塔传说:3根柱子A、B、C,A上有n个盘子,大盘在下小盘在上,要把所有盘子从A移到C,每次只能移1个,且大盘不能压小盘。 | 用3个实物(如杯子和小积木)现场演示n=2和n=3的移动过程,让学生观察规律。 |
分析步骤 | 以n=3为例,分解为3步:1. 把上面2个盘子从A移到B(借助C);2. 把第3个盘子从A移到C;3. 把B上的2个盘子从B移到C(借助A)。 | 板书“汉诺塔递归步骤”,引导学生发现:移动n个盘子 = 移动n-1个盘子(2次) + 移动1个盘子(1次)。 |
代码框架 | 定义函数void hanoi(int n, char from, char to, char aux):- 终止条件:n == 1时,直接移动;- 递推关系:1. hanoi(n-1, from, aux, to);// 把n-1个从from移到aux2. 移动第n个盘子:从from到to;3. hanoi(n-1, aux, to, from);// 把n-1个从aux移到to。 | 板书代码框架,不要求完整实现,重点理解“分而治之”的思想。学生尝试补充“移动第n个盘子”的输出语句。 |
分组讨论 | 讨论:如果A柱上有4个盘子,需要移动多少次? | 学生分组计算,老师引导:n=1时1次,n=2时3次,n=3时7次,n=4时15次……规律是2^n - 1次。 |
2. 代码评审与优化(15:40–17:00)
环节 | 内容 | 形式与互动 |
|---|
学生代码展示 | 选取3–4名学生的代码投影展示,包括:1. 阶乘函数(正确/错误版本);2. 斐波那契函数(递归/非递归版本);3. 最大公约数函数。 | 学生自愿举手提交代码,老师逐一点评:- 优点:如代码规范、注释清晰;- 问题:如缺少终止条件、参数类型错误、递归逻辑混乱。 |
递归效率问题 | 以斐波那契数列为例,分析递归的“重复计算”问题:求fib(5)时,fib(3)被计算了2次;求fib(10)时,fib(3)被计算了21次!引出优化方法:记忆化搜索(用数组保存已计算的结果,避免重复计算)。 | 板书“记忆化搜索”代码框架:```cppint memo[100]; // 记忆数组,初始化为-1int fib(int n) {if (n == 1 |
常见问题答疑 | 解答学生本周自学中遇到的问题:- 函数参数传递时“地址符号*”的用法;- 递归函数中“返回值类型”的选择(如void递归函数如何处理结果);- 递归深度过大导致栈溢出怎么办?(入门期暂不深入,点到为止)。 | 学生举手提问,老师针对性解答,必要时现场写代码片段演示。 |
3. 本周作业布置(17:00–17:15)
作业内容 | 要求 |
|---|
1. 完成洛谷题目:- P1028 [NOIP2001 普及组] 数的计算(简单递归)- P1036 [NOIP2002 普及组] 选数(递归枚举)2. 预习:排序算法(冒泡排序、选择排序) | - 每道题写出解题思路(文字描述或流程图);- 提交代码时附带注释,说明递归终止条件和递推关系;- 预习时尝试手写冒泡排序代码。 |
三、周日专项特训:递归与函数综合应用(3小时)
1. 模拟测试(9:00–10:00)
测试题目 | 考察重点 |
|---|
T1:阶乘递归(洛谷P1028改编)输入n,输出n!(n≤12)。 | 递归终止条件、返回值正确性。 |
T2:斐波那契数列(洛谷P1036改编)输入n,输出第n个斐波那契数(n≤30)。 | 递归递推关系、处理n较小的情况(避免溢出)。 |
T3:最大公约数(原创)输入两个整数a,b,输出它们的最大公约数。 | 递归函数参数传递(地址传递vs值传递)、辗转相除法逻辑。 |
评分标准:
代码正确性(60%);
递归三要素是否清晰(20%);
代码规范(注释、缩进,20%)。
2. 错题复盘与总结(10:10–12:00)
环节 | 内容 | 形式与互动 |
|---|
试卷点评 | 逐题讲解测试卷,分析高频错误:- T1:忘记处理n=0的情况(0! = 1);- T2:递归终止条件写成n==0(应从n==1或n==2开始);- T3:参数传递时用值传递导致无法交换(若题目要求用函数交换两个数)。 | 投影展示典型错误代码,让学生指出问题所在,老师补充讲解。 |
递归思维导图 | 和学生一起绘制“递归思维导图”,梳理核心要点:- 递归三要素(终止条件、递推关系、返回处理);- 适用场景(问题可分解为相似子问题);- 优缺点(代码简洁vs效率低)。 | 学生在笔记本上同步绘制,老师用不同颜色粉笔标注重点(如终止条件用红色)。 |
阶段总结 | 总结本周学习内容:1. 函数是“代码复用”的工具,需掌握定义、调用、参数传递;2. 递归是“分而治之”的思想,核心是找到“小问题与大问题相同的解决方式”;3. 递归需注意终止条件,避免死循环和重复计算。 | 邀请1–2名学生分享“本周最大的收获”,老师给予肯定和鼓励。 |
下周预告 | 预告下周主题:排序与查找,将学习冒泡排序、快排思想、二分查找,以及STL sort的使用。 | 展示下周要讲的“冒泡排序”动画,激发学生学习兴趣。 |
四、配套资源
《函数与递归避坑手册》
《递归流程图模板》
推荐视频
一、课堂互动小游戏(配合周六上午“函数与递归精讲”)
游戏1:函数“传话游戏”(9:30–9:45,函数参数传递环节)
目的:让学生直观感受值传递 vs 地址传递的区别。
玩法:
选 3 个学生 A、B、C,再选 1 个学生 D 当“记录员”。
老师悄悄给 A 一个数字(比如 5),告诉 A:
B 是“地址传递”,老师给 B 看原始纸条,B 按规则“+1”后,直接在纸条上改。
最后让 A、B 分别报出自己手里的数字,D 记录。
老师问全班:A 和 B 手里的数字分别是多少?为什么不一样?
代码对应:
效果:
学生通过“抄数字”和“改纸条”的对比,很容易理解:
值传递只是改了“复印件”,原件不变;
地址传递是改了“原件本身”。
游戏2:递归“故事接龙”(10:40–10:55,递归思想引入环节)
目的:用故事理解“自己调用自己”的递归结构,并自然引出终止条件。
玩法:
老师先说第一句:
“从前有座山,山里有座庙,庙里有个老和尚在给小和尚讲故事。”
然后老师说:
“故事的内容是——从前有座山,山里有座庙,庙里有个老和尚在给小和尚讲故事……”
邀请学生接龙,要求必须原样重复这句话,不能改词。
连续接 3 轮后,老师突然打断:
“等一下,这个故事能一直讲下去吗?如果不加个‘结束’,我们会怎样?”
引导学生说出:会一直讲不完,就像死递归。
代码对应:
故事内容 = 递归函数体;
接龙 = 自己调用自己;
必须加一句“讲完了” = 终止条件。
效果:
学生笑一笑的同时,就记住了:
游戏3:汉诺塔“真人版挑战”(14:00–14:20,汉诺塔思想实践环节)
目的:用身体参与的方式,理解“分而治之”的递归步骤。
准备:
3 张桌子/椅子,分别当 A、B、C 三根柱子;
3–5 个大小不同的物品,如:
大纸箱 = 大盘子
小盒子 = 小盘子
橡皮 = 最小盘子
玩法:
把 3 个物品按“大在下、小在上”放在 A 桌。
老师宣布规则:
一次只能移动一个物品;
大盘子不能压在小盘子上;
目标是把所有物品从 A 移到 C。
先让全班一起想:
2 个物品时,怎么移?
3 个物品时,能不能先动最下面的?
请 1 名学生上来,按“先移上面 2 个,再移最下面 1 个,再把 2 个移过去”的思路试一遍。
老师边看边说:
效果:
学生通过亲自“搬箱子”,能直观看到:
二、趣味例题(配合周六下午“实战演练 + 代码评审”)
趣味题1:递归求“猴子吃桃”(15:00–15:20,函数综合应用)
题目背景:
猴子第一天摘了若干个桃子,当天就吃掉一半再多 1 个;
第二天又吃掉剩下的一半再多 1 个;
以后每天都这样吃,到第 10 天早上只剩 1 个桃子。
问:第一天摘了多少个桃子?
代码实现(递归):
cpp
下载
复制
#include<iostream>usingnamespace std;// day: 第几天早上(day=10 时剩1个,day=1 是第一天早上)intpeach(int day){if (day == 10) { // 终止条件:第10天早上剩1个return1; }// 递推关系:第day天的桃子 = (第day+1天的桃子 + 1) * 2return (peach(day + 1) + 1) * 2;}intmain(){ cout << "第一天摘了 " << peach(1) << " 个桃子" << endl;return0;}
互动设计:
亮点:
趣味题2:函数“角色扮演游戏”(RPG)伤害计算器(15:20–15:40,函数综合应用)
题目背景:
设计一个简单的“打怪”小系统:
代码实现:
cpp
下载
复制
#include<iostream>#include<cstdlib>#include<ctime>usingnamespace std;// 计算单次伤害intcalcDamage(int atk, int def, int critRate){int damage = atk - def;if (damage < 0) damage = 0; // 伤害不能为负// 随机数模拟暴击(0~99 与 critRate 比较)if (rand() % 100 < critRate) { damage *= 2; }return damage;}// 计算n次攻击总伤害inttotalDamage(int atk, int def, int critRate, int n){int sum = 0;for (int i = 0; i < n; i++) { sum += calcDamage(atk, def, critRate); }return sum;}intmain(){srand(time(0)); // 随机种子int atk = 20, def = 5, critRate = 30, n = 3; cout << n << " 次攻击总伤害:" << totalDamage(atk, def, critRate, n) << endl;return0;}
互动设计:
亮点:
趣味题3:递归“谢尔宾斯基三角形”(选做,16:00–16:20,拓展提升)
题目背景:
用“*”画一个谢尔宾斯基三角形:
第 1 级:1 个“*”;
第 2 级:3 个“*”,排成小三角形;
第 3 级:把每个小三角形再分成 3 个更小的三角形……
用递归函数 draw(int level, int x, int y)实现,其中:
level是当前层级;
(x, y)是当前三角形的顶点位置。
代码实现(核心逻辑):
cpp
下载
复制
#include<iostream>usingnamespace std;voiddraw(int level, int x, int y){if (level == 0) { // 终止条件:层级为0,画一个点 cout << "*";return; }// 画当前层级的三角形(简化为打印3个子问题)draw(level - 1, x, y); // 左上draw(level - 1, x + (1 << (level-1)), y); // 右上draw(level - 1, x + (1 << (level-2)), y + (1 << (level-1))); // 下方}intmain(){int level = 3;draw(level, 0, 0);return0;}
互动设计:
先在纸上画:
再问:
让学生用“分形”的角度理解:
大三角形 = 3 个小三角形 + 某种排列;
这就是典型的递归分形结构。
亮点:
三、使用建议
时间控制
时间充裕时,讲完主例题后再讲一个;
时间紧时,可以只做游戏,把趣味题留作课后选做。
与知识点强绑定
学生参与感
难度分级