递归简介
定义
在定义一个过程或函数时,出现直接或者间接调用自己的成分,称为递归。
简而言之,递归就是函数自己调用自己,其过程与一个函数调用其他函数相同。
分类
直接递归:直接调用自己, 形如
void f(){
...
f();
...
}
间接递归:间接调用自己, 形如
void g(){
...
f();
...
}
void f(){
...
g();
...
}
递归实现原理
递归用的是分而治之思想,即将大问题不断分解为小问题,如果小问题还不能解决,再将小问题分解为更小的问题,直到小问题能够解决。
递归函数基本由两部分组成:
递归出口:递归函数终止条件。如果没有递归出口,将导致程序进入死循环,直至栈满溢出。
递归体:确定递归求解时的递推关系。
任何递归函数都可分为两个过程:
分解:将大问题逐渐拆分为小问题。
求解:由小问题往回求解大问题。
以 n!举例,f(n)的功能为求n的阶乘,代码如下
int f(const int n) {
if (1 == n)
return 1;
return n * f(n - 1);
}
分解:
大问题:求解 n !
拆为:n * (n - 1)!
继续拆: n * (n - 1) * (n - 2) !
…
直到: 1!
此时可以知道1! = 1, n == 1即为递归出口
求值:
由1!可求得2!
由2! 可求得 3!
…
由此可求得n!
n!就得到了解决
实例
阶乘
#include<iostream>
using namespace std;
int f(const int n) {
if (1 == n)
return 1;
return n * f(n - 1);
}
int main()
{
cout << f(5) << endl;
return 0;
}
输出结果为
120
累加
#include<iostream>
using namespace std;
int f(const int n) {
if (1 == n)
return 1;
return n + f(n - 1);
}
int main()
{
cout << f(100) << endl;
return 0;
}
函数体大致与阶乘相同,输出结果为 5050
Fibonacci数列
Fibonacci数列介绍
1, 1,2,3, 5,8,13,21, 34,55,...
即函数第n位上的数始终是第n-1和n-2位置上的数的和,第1位和第2位为1
求Fibonacci数列第n位上的值
迭代实现
#include<iostream>
using namespace std;
int Fibo_Iter(const int n) {
if (1 == n || 2 == n)
return 1;
int n1 = 1, n2 = 1, n3;
for (int i = 3; i <= n; ++i) {
n3 = n1 + n2;
n1 = n2;
n2 = n3;
}
return n2;
}
int main()
{
for (int i = 1; i != 10; ++i)
cout << Fibo_Iter(i) << " ";
return 0;
}
输出为
递归实现
#include<iostream>
using namespace std;
int Fibo_Recur(const int n) {
if (1 == n || 2 == n)
return 1;
return Fibo_Recur(n - 1) + Fibo_Recur(n - 2);
}
int main()
{
for (int i = 1; i != 10; ++i)
cout << Fibo_Iter(i) << " ";
return 0;
}
输出结果与迭代结果一致
汉诺塔
规则:将A柱上的圆盘通过B柱移动在C柱,一次只能移动一个圆盘,且大圆盘不能放在小圆盘上面
分析
假设A柱上一共有n个圆盘,问题可以拆解为
- 将A上的n-1个圆盘通过C移动到B上
- 再将A上第n个(最下面那个)圆盘移动到C上
此时再将上面第1步分解为
- 将B上的n-2个圆盘通过C移动到A上
- 将B上的第n-1个圆盘移动到C上
再分解上面第1步
…
到最后一步时,只剩1个圆盘在A或者B上,此时将这最后一个圆盘移动到C上,即完成了n个圆盘从A移动到C的任务。
代码如下
#include<iostream>
using namespace std;
void Hanoi(const int n, const char A, const char B, const char C)
//将A上的n个圆盘,通过B,移动到C
{
if (1 == n) //函数出口
{
cout << A << "->" << C << endl;
return;
}
Hanoi(n - 1, A, C, B); //将A上n-1个圆盘通过C移动到B
cout << A << "->" << C << endl; //将A上第n个圆盘移动到C
Hanoi(n-1, B, A, C); //将B上n-1个圆盘通过A移动到C
}
int main()
{
Hanoi(3, 'X', 'Y', 'Z'); //3表示最初A上有3个圆盘,可以自行更改
return 0;
}
运行结果如下
递归的优缺点
优点
递归算法使算法代码更加简洁易懂,在某些情况下,效果尤为明显,如树的遍历、汉诺塔游戏、走迷宫
缺点
- 由于递归是通过不断地弹栈和压栈实现的,每一次压栈都会进行参数拷贝、空间分配等过程,因此会耗费大量的时间和空间,一次调用结束会进行弹栈,也需要耗费时间。
- 递归出口设计不合理会导致调用栈溢出
在程序中选用递归算法还是迭代算法需要考虑到递归的深度,在递归次数大于一定值时(我的电脑上为20),递归的效率的非常低了。