递归
概念
- 概念上简化了问题,不一定效率高
- 特殊的嵌套调用,函数自己调用自己
- 比起非递归,可能会对存储空间的要求较高
条件
- 问题可以化为一个或多个小问题,且于问题的求解方法与原问题相同
- 调用的次数有限
- 须有结束条件来终止递归
一般步骤
- 将规模较大的原问题分解为一个或多个规模较小的且类似原问题特性的子问题,即将较大问题递归的用小问题来描述,用来解原问题的方法也可以用来解决子问题(递归的步骤)
- 确定一个或多个不需要分解的,可以直接求解的最小子问题(递归的结束条件)
// 例1、阶乘:
// Factorial == 阶乘
int fac(int n){
int temp;
if(n == 0){
return 1; // 递归的结束条件
}else{
temp = n * fac(n-1);
return temp; // 递归调用
}
}
3!
int fac(3){
int temp;
if(n == 0){
return 1; // 递归的结束条件
}else{
temp = 3 * fac(2) = 3 * 2 * fac(1) = 3 * 2 * 1 * fac(0) = 3 * 2 * 1 * 1;
return temp; // 递归调用
}
}
// 例2、斐波那契
/*Fibonacci == 斐波那契;
*数学家斐波那契以兔子繁殖为例子而引入,故又称为“兔子数列”
*指的是这样一个数列:1、1、2、3、5、8、13、21、34、...
*数学上以如下被以递推的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)
*/
int fib(int){
if(n == 0 || n == 1){
return n; // 递归的结束条件
}else{
return(fib(n - 2) + fib(n - 1)); // 递归调用
}
}
内部原理
分为俩阶段,这个过程是用 栈 完成的
- 递归过程 不断缩小问题规模。比如从求 3! 到求 2! 最终到达递归结束条件,求 1!
- 为被调用过程的局部变量分配储存区;
- 将所有的实参、返回地址等信息传递给被调用过程保存;
- 将控制转移到被调过程的入口。
- 回溯(su)过程 从已知条件出发,沿着递归的逆过程,逐一求返回值,知道递归初始处,完成递归调用。
- 保存被调过程的计算结果;
- 释放被调过程的数据区;
- 依照被调过程的返回地址将控制转移到调用过程。
递归与非递归的转化
递推
当地贵算法设计的数据定义形式是递归的情况新,通常可以将递归算法转化为递推算法,把递归的边界条件作为递推的边界条件。
地推也是从已知条件出发,用一种具体的算法,一步步接近未知,常采用循环结构,与枚举配合使用。在求解过程中,每一个中间量都是已知的,没有重复计算。
// 例3、阶乘:
// Factorial == 阶乘
int fac(int n){
int f = 1;
for(int i = 1;i <= n;i ++){
f = f * i;
}
return f;
}
// 例4、斐波那契
/*Fibonacci == 斐波那契;
*数学家斐波那契以兔子繁殖为例子而引入,故又称为“兔子数列”
*指的是这样一个数列:1、1、2、3、5、8、13、21、34、...
*数学上以如下被以递推的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)
*/
int fib(int){
int f0 = 1;
int f1 = 1;
int f;
if(n <= 1){return 1;}
for(int i = 0;i < n-1;1 ++){
f = f0 + f1;
f0 = f1;
f1 = f;
}
}
递归和递推的区别【两者不是逆过程】
递归 | 递推 |
---|---|
从未知到已知,再从已知返回未知,利用子问题和父问题的关系构成有递归性的函数 | 从已知到未知,类似一般的解题思路 |
把问题简单化,抓的是问题和子问题的联系 | 抓的是中间量和更靠近位置的中间量的联系 |
回溯
步骤:
- 定义一个包含问题的解的解空间
- 用适于搜索的方式组织这个空间
- 用深度优先算法搜索此空间,利用限界函数避免移动到不可能产生解的子空间