文章目录
递归的作用
1. 用递归来完成递推
方法:
- 求解目标:把关注点放在要求解的目标上。
- 关系:找到第n次与第n-1次之间的关系。
- 初始值:确定第1次返回结果。
1.1 n的阶乘
1的阶乘:1! = 1;
2的阶乘:2! = 2 x 1;
…
n的阶乘:n! = n x (n-1) x … x 2 x 1.
#include <iostream>
using namespace std;
// 下面的函数实现n的阶乘
int factorial(int n)
{
if (n == 1) // 如果n=1,则阶乘为1
{
return 1;
}
else // 如果n!=1, 则阶乘为n乘以(n-1)的阶乘。
{
return (n * factorial(n-1));
}
}
int main()
{
int n = 0;
cout << "输入数字, 得到它的阶乘:" << endl;
// 不断从控制台读取数据,直到读入Ctrl + z为止。
while (cin >> n)
{
cout << factorial(n) << endl;
}
return 0;
}
1.2 斐波那契数列
斐波那契数列指的是这样一个数列:1, 1, 2, 3, 5, 8, 13, 21, 34 …
递推公式为:
F(1) = 1;
F(2) = 1;
F(n) = F(n-1) + F(n-2).
#include <iostream>
using namespace std;
// 本函数实现斐波那契数列
int fibonacci(int n)
{
if (n ==1)
return 1;
else if (n == 2)
return 1;
else
return (fibonacci(n-1) + fibonacci(n-2));
}
int main()
{
int n = 0;
cout << "输入斐波那契数列的序号:" << endl;
// 不断从控制台读取数据,直到读入Ctrl + z为止。
while (cin >> n)
{
cout << fibonacci(n) << endl;
}
return 0;
}
2. 模拟连续发生的动作
方法:
- 连续动作:搞清楚连续发生的动作是什么。
- 关系: 搞清楚不同动作之间的关系。
- 边界条件:搞清楚边界条件。
2.1 十进制转二进制
这里我使用的方法是:除2取余,逆序排列。
给一个十进制的整数,一直除以2取余数,直到商为0。然后将所有的余数逆序排列,即为对应的二进制数。
例如:
123 == 1111011
123 / 2 = 61…余1
61 / 2 = 30…余1
30 / 2 = 15…余0
15 / 2 = 7…余1
7 / 2 = 3…余1
3 / 2 = 1…余1
1 / 2 = 0…余1
#include <iostream>
using namespace std;
// 下面的函数实现十进制转二进制
void decimal_to_binary(int n)
{
if ((n/2) == 0)
{
cout << n;
}
else
{
// 注意下面两行代码不能调换顺序
// 逆序排列余数,生成二进制
decimal_to_binary(n/2);
cout << (n % 2);
}
}
int main()
{
int n = 0;
cout << "输入十进制数,将其转化为二进制数:" << endl;
// 不断从控制台读取数据,直到读入Ctrl + z为止。
while (cin >> n)
{
decimal_to_binary(n);
cout << endl;
}
return 0;
}
2.2 汉诺塔问题
汉诺塔传说:
汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
具体问题:
有三根相邻的柱子,标号为A, B, C,A柱子上从下到上按金字塔状叠放着n个不同大小的圆盘,要把所有盘子一个一个移动到柱子C上,并且每次移动同一根柱子上都不能出现大盘子在小盘子上方,请问要如何移动?
解决方法:
最简单的情况:
如果只有一个圆盘,则直接从A移到C即可,A -> C。
最复杂的情况:
如果有n个圆盘,则可以分解三步:
(1) 将A柱子上的上面n-1个圆盘搬到B柱子上;
(2) 再将A柱子上的第n个圆盘移动到C柱子上。
(3) 最后将B柱子上的n-1个圆盘移动到C柱子上。
如下图所示:
#include <iostream>
using namespace std;
void hanoi_tower(int n, char a, char b, char c);
void move(int numDisk, char a, char b, char c);
int main(){
int n = 0;
cout << "请输入圆盘数量:" << endl;
while (cin >> n){
int count = 0; // 保存移动次数
char a = 'A', b = 'B', c = 'C';
// 将n个盘子从A经过B移动到C
hanoi_tower(n, a, b, c);
cout << endl;
}
return 0;
}
// 下面的函数实现汉诺塔问题
// 将n个盘子从A经过B移动到C
void hanoi_tower(int n, char a, char b, char c){
if (n == 1){
move(1, a, b, c);
}
else {
hanoi_tower(n-1, a, c, b);
move(n, a, b, c);
hanoi_tower(n-1, b, a, c);
}
}
// 将盘子从A移动到C
void move(int numDisk, char a, char b, char c)
{
cout << a << " -> " << c << endl;
}
3. 进行“自动的分析”
方法:
- 先假设,有一个函数能给出答案。
- 在利用这个函数的前提下,分析如何解决问题。
- 搞清楚最简单的情况下,答案是什么。
3.1 波兰表达式 (前缀表达式)
波兰表达式描述:
波兰表达式是一种把运算符前置的算术表达式:
- 如 2 + 3 的波兰表达式为 + 2 3
- 如 (2 + 3) * 4 的波兰表达式为 * + 2 3 4
我们需要编程求解出包含 + - * / 四个运算符的波兰表达式的值。
示例1:
输入:* + 11.0 12.0 + 24.0 35.0
输出:1357.0
示例2:
输入:* / + 12 36 + 1 3 - 15 8
输出:84
解决方法:
- 假设有一个函数calculator()可以计算波兰表达式。
- 分析波兰表达式,可以得到解答步骤。那么在当遇到运算符时,调用函数。如下所示:
‘+’: return calculator() + calculator();
‘-’: return calculator() - calculator();
‘*’: return calculator() * calculator();
‘/’: return calculator() / calculator();
当遇到数值时,调用函数如下:
return atof(str);
#include <iostream>
using namespace std;
// 下面函数可以求解波兰表达式
double calculator()
{
char str[10];
cin >> str;
switch (str[0])
{
case '+':
return calculator() + calculator();
case '-':
return calculator() - calculator();
case '*':
return calculator() * calculator();
case '/':
return calculator() / calculator();
default:
return atof(str); // 将字符串转化为数值
}
}
int main()
{
cout << calculator();
return 0;
}
3.2 放苹果
描述:
把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问有多少种不同的分法?(所有的苹果都一样,所有的盘子都一样。)
注意:5, 1, 1 和 1, 5, 1 是同一种分法,与排列顺序无关。
示例1:
输入:7 3
输出:8
示例2:
输入:100 100
输出:190569292
解决方案:
- 假设有个函数count(m, n)可以解决放苹果问题。
- 分析:因为盘子都是一样的,所以不是排列问题,而是苹果的组合问题。苹果的数量是M,盘子的数量是N。分两种情况讨论:
(1) 当M < N时,多余的盘子都不影响苹果的分法。所以可以得出count(m, n) == count(m, m);
(2) 当M >= N时,又分两种情况:一是有空盘子的情况,则至少有一个空盘子,空盘子去掉不会影响苹果分法,因此count(m, n) ==count(m, n-1)。二是没有空盘子的情况,则每个盘子里面至少有一个苹果,去掉每个盘子里的那一个苹果,不会影响苹果分法,因此count(m, n) == count(m-n, n)。 - 边界条件:当M <= 1或者N <= 1时,只有一种分法,因此count(m, n) == 1。
#include <iostream>
using namespace std;
// 下面函数能够解决放苹果问题
int count(int m, int n)
{
// 边界条件
// 当M <= 1或者N <= 1时,只有一种分法,因此count(m, n) == 1。
if (m <= 1 || n <= 1)
{
return 1;
}
// 当M < N时,多余的盘子都不影响苹果的分法。
// 所以可以得出count(m, n) == count(m, m);
if (m < n)
{
return count(m, m);
}
// 当M >= N时,又分两种情况:
// 一是有空盘子的情况,则至少有一个空盘子,空盘子去掉不会影响苹果分法,
// 因此count(m, n) == count(m, n-1)。
// 二是没有空盘子的情况,则每个盘子里面至少有一个苹果,去掉每个盘子里的那一个苹果,
// 不会影响苹果分法,因此count(m, n) == count(m-n, n)。
else
{
return (count(m, n-1) + count(m-n, n));
}
}
int main()
{
int m = 0, n = 0;
cin >> m >> n;
cout << count(m, n) << endl;
return 0;
}
说明
上面三个场景没有明显的界限,道理都是相通的,不用刻意划分界限。
参考:Coursera课程《C程序设计进阶》