递归进阶
一、递归的概念
1. 意义
递归,就是函数自己调用自己。
2. 要素
递归有三个要素:函数、公式和边界。
二、整数加法拆分
1. 审题
编写程序,将一个不超过 50 50 50 的正整数 n n n 分解成所有不同的数相加的式子,例如输入 5 5 5 时,输出:
5=5
5=4+1
5=3+2
5=3+1+1
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
2. 思路
我们可以使用一个 dfs(x)
递归函数,其边界是参数
x
x
x 为
0
0
0 的时候。这个函数的工作原理很简单,就是将当前的数字每次都拆分成两个数字,然后将较大数字继续拆分下去,即:
void dfs(int x)
{
if (x == 0) return;
for (int i = x ~ 1)
{
dfs(x-i);
}
}
如果 n n n 是 5 5 5,那么其执行原理为:
3. 参考答案
#include <iostream>
using namespace std;
int n;
int a[55];
void dfs(int x, int pos)
{
if (x == 0)
{
cout << a[0] << "=" << a[1];
for (int i = 2; i <= pos-1; i++)
{
cout << "+" << a[i];
}
cout << endl;
return;
}
for (int i = min(x, a[pos-1]); i >= 1; i--)
{
a[pos] = i;
dfs(x-i, pos+1);
}
}
int main()
{
cin >> n;
a[0] = n;
dfs(n, 1);
return 0;
}
三、汉诺塔移动过程
1. 审题
汉诺塔游戏,是将所有圆盘从一个柱子移动到另一个柱子的一宗游戏。每次只能移动其中一个柱子最上面的一个圆盘,大圆盘不能放在小圆盘上。编写程序,现在给定圆盘数量 n n n,将移动过程写出。
2. 思路
n | 汉诺塔移动方法 |
---|---|
1 | A=>C |
2 | A=>B, A=>C, B=>C |
3 | A=>C, A=>B, C=>B, A=>C, B=>A, B=>C, A=>C |
4 | A=>B, A=>C, B=>C, A=>B, C=>A, C=>B, A=>B, A=>C, B=>C, B=>A, C=>A, B=>C, A=>B, A=>C, B=>C |
5 | A=>C, A=>B, C=>B, A=>C, B=>A, B=>C, A=>C, A=>B, C=>B, C=>A, B=>A, C=>B, A=>C, A=>B, C=>B, A=>C, B=>A, B=>C, A=>C |
步骤:
- 【打开冰箱】
将上方的 n − 1 n-1 n−1 个盘子从 A A A 柱(起始柱)移动到 B B B 柱(临时柱)。借助 C C C 柱作为辅助柱。 - 【放进大象】
将最大的第 n n n 个盘子从 A A A 柱(起始柱)移动到 C C C 柱。 - 【关上冰箱】
将 B B B 柱(临时柱)上的 n − 1 n-1 n−1 个盘子移动到 C C C 柱(终点柱)。借助 A A A 柱作为辅助柱。
我们可以编写一个 hanoi()
递归函数(其表示一个放的操作),其传入参数为,功能为将
n
n
n 个盘从
q
q
q 移动到
z
z
z,借助临时柱
t
m
p
tmp
tmp:
void hanoi(int n, char q, char z, char tmp)
{
if (n == 1)
{
cout << q << "->" << z << endl;
return;
}
hanoi(n-1, q, tmp, z);
cout << q << "->" << z << endl;
hanoi(n-1, tmp, z, q);
}
3. 参考答案
#include <iostream>
using namespace std;
int n;
void hanoi(int n, char q, char z, char tmp)
{
if (n == 1)
{
cout << q << "->" << z << endl;
return;
}
hanoi(n-1, q, tmp, z);
cout << q << "->" << z << endl;
hanoi(n-1, tmp, z, q);
}
int main()
{
cin >> n;
hanoi(n, 'A', 'C', 'B');
return 0;
}
四、青蛙过河
1. 审题
大小各不相同的一队青蛙站在河左岸的石墩(记为
A
A
A)上,要过到对岸的石墩(记为
D
D
D)上去。河心有几片菏叶(分别记为
Y
1
,
Y
2
,
.
.
.
,
Y
m
Y_1, Y_2, ..., Y_m
Y1,Y2,...,Ym)和几个石墩(分别记为
S
1
,
S
2
,
.
.
.
,
S
n
S_1, S_2, ... ,S_n
S1,S2,...,Sn)。
青蛙的站队和移动方法规则如下:
- 每只青蛙只能站在荷叶、石墩,或者仅比它大一号的青蛙背上(统称为合法的落脚点);
- 一只青蛙只有背上没有其它青蛙的时候才能够从一个落脚点跳到另一个落脚点;
- 青蛙允许从左岸 A A A 直接跳到河心的石墩、荷叶和右岸的石墩 D D D 上,允许从河心的石墩和荷叶跳到右岸的石墩 D D D 上;
- 青蛙在河心的石墩之间、荷叶之间以及石墩和荷叶之间可以来回跳动;
- 青蛙在离开左岸石墩后,不能再返回左岸;到达右岸后,不能再跳回;
- 假定石墩承重能力很大,允许无论多少只青蛙都可呆在上面。但是,由于石墩的面积不大,至多只能有一只青蛙直接站在上面,而其他的青蛙只能依规则 1 1 1 落在比它大一号的青蛙的背上。
- 荷叶不仅面积不大,而且负重能力也有限,至多只能有一只青蛙站在上面。
- 每一步只能移动一只青蛙,并且移动后需要满足站队规则;
- 在一开始的时候,青蛙均站在 A A A 上,最大的一只青蛙直接站在石墩上,而其它的青蛙依规则 6 6 6 站在比其大一号的青蛙的背上。
- 青蛙希望最终能够全部移动到
D
D
D 上,并完成站队。
设河心有 m m m 片荷叶和 n n n 个石墩,请求出这队青蛙至多有多少只,在满足站队和移动规则的前提下,能从 A A A 过到 D D D。
你的任务是对于给出的 n , m n,m n,m,计算并输出最多能有多少只青蛙可以根据以上规则顺利过河。
2. 思路
公式法:
荷叶数目 | 最多青蛙 |
---|---|
0 | 1 |
1 | 2 |
2 | 3 |
3 | 4 |
… | … |
h h h | h + 1 h+1 h+1 |
石墩子数目 | 荷叶数目 | 最多青蛙 |
---|---|---|
0 | 1 | 2 |
1 | 1 | 4 |
2 | 1 | 8 |
3 | 1 | 16 |
4 | 1 | 32 |
0 | 2 | 3 |
1 | 2 | 6 |
2 | 2 | 12 |
3 | 2 | 24 |
4 | 2 | 48 |
… | … | … |
s s s | h h h | ( h + 1 ) × 2 (h+1)\times2 (h+1)×2 |
无石墩:青蛙
=
h
+
1
=h+1
=h+1
有石墩:青蛙
=
(
h
+
1
)
×
2
=(h+1)\times2
=(h+1)×2
公式:
a
n
s
=
(
h
+
1
)
×
2
s
ans=(h+1)\times2^s
ans=(h+1)×2s
递归法
int f(int s, int h)
{
if (s == 0) return (h+1);
return 2 * f(s-1, h);
}
3. 参考答案
#include <iostream>
// #include <cmath>
#include <cstdio>
using namespace std;
int n, m;
long long f(int s, int h)
{
if (s == 0) return (h+1);
return 2 * f(s-1, h);
}
int main()
{
freopen("frog.in", "r", stdin);
freopen("frog.out", "w", stdout);
cin >> n >> m;
cout << f(n, m);
// cout << (m+1) * pow(2, n);
fclose(stdin);
fclose(stdout);
return 0;
}
练习
1. 整数乘法拆分
1.1 审题
编写程序,将一个不超过 50 50 50 的正整数 n n n 分解成所有不同的数相乘的式子,例如输入 8 8 8 时,输出:
8=8
8=4*2
8=2*2*2
1.2 参考答案
#include <iostream>
using namespace std;
int n;
int a[55];
void dfs(int x, int pos)
{
if (x == 1)
{
cout << a[0] << "=" << a[1];
for (int i = 2; i <= pos-1; i++)
{
cout << "*" << a[i];
}
cout << endl;
return;
}
for (int i = min(x, a[pos-1]); i >= 2; i--)
{
if (x % i == 0)
{
a[pos] = i;
dfs(x/i, pos+1);
}
}
}
int main()
{
cin >> n;
a[0] = n;
dfs(n, 1);
return 0;
}
2. 汉诺塔最少移动数量
2.1 审题
汉诺塔游戏,是将所有圆盘从一个柱子移动到另一个柱子的一宗游戏。每次只能移动其中一个柱子最上面的一个圆盘,大圆盘不能放在小圆盘上。编写程序,现在给定圆盘数量 n n n,求出最小移动次数。
2.2 思路
我们可以代入 二、 中所写的程序,反推出最小移动次数为 2 n − 1 2^n-1 2n−1。我们可以直接输出。
而这个推出的公式原理是什么呢?最后还是回到我们发现的规律:
- 【打开冰箱】
将上方的 n − 1 n-1 n−1 个盘子从 A A A 柱(起始柱)移动到 B B B 柱(临时柱)。借助 C C C 柱作为辅助柱。 - 【放进大象】
将最大的第 n n n 个盘子从 A A A 柱(起始柱)移动到 C C C 柱。 - 【关上冰箱】
将 B B B 柱(临时柱)上的 n − 1 n-1 n−1 个盘子移动到 C C C 柱(终点柱)。借助 A A A 柱作为辅助柱。
f
(
n
)
=
f
(
n
−
1
)
+
1
+
f
(
n
−
1
)
=
2
×
f
(
n
−
1
)
+
1
f(n) = f(n-1) + 1 + f(n-1)=2\times f(n-1)+1
f(n)=f(n−1)+1+f(n−1)=2×f(n−1)+1
f
(
n
)
=
2
n
−
1
f(n)=2^n-1
f(n)=2n−1