基本思想
- 把规模较大的一个问题,分解成规模较小的多个子问题去解决,而每一个子问题又可以继续拆分成多个更小的子问题。
- 递归解决的是有依赖关系的多个问题:必须先解决最小子问题,在层层递进的方式解决当前问题
广义递归
void main(){
A();
}
func_A()
{
func_B();
}
func_B()
{
func_C();
}
func_C()
{
.....//代码块儿
}
具体过程:
- 调用函数A;
- 调用函数B;
- 调用函数C;
- 函数C返回;
- 函数B返回;
- 函数A返回;
狭义递归
void main(){
A();
}
func_A(int i)
{
if(i == 1) return 1;
return A(i-1)+1;
}
具体过程:
当 i = 3 时,函数 A 被调用了三次,与广义递归中的调用,从某种形式上看一致。
我们把函数内调用函数自身的形式,称为狭义递归
递归与栈的紧密关系
从基本思想来说
- 从递归的基本思想及对问题的处理顺序来看,是遵循着 “先开始的问题,最后结束” 的规律
- 栈对数据的操作,即是 “先进后出”,两者不谋而合了
从函数栈的使用角度说
void dfs(int a0, int a1, ....){
{
//代码块儿 0;
}
dfs(a00, a11, ...);
{
//代码块儿 1;
}
}
执行过程如下:
整体的递归过程
(以代码被执行的第一视角来看:)
1、执行代码块儿0
2、保存当前情景进入下一层
3、接受下层返回的数据
4、恢复情景
5、继续执行代码块儿1
栈的实际使用
第 2,4 步骤,都是编译器在完成,及在前行阶段,对于每一层递归,函数的局部变量、参数值以及返回地址都被压入栈中。在回退阶段,位于栈顶的局部变量、参数值和返回地址被弹出,用于返回调用层次中执行代码的其余部分(恢复了调用的状态)。
栈与递归的实际功用
(1)浏览器的撤回功能
不管什么浏览器都有一个“后退”键,你点击后可以按访问顺序的逆序加载浏览过的网页。即使你从第一个网页开始,连续点了几十个链接跳转,你点“后退”时,还是可以像历史倒退一样,回到之前浏览过的某个页面
(2)函数的调用
int main() {
a();
return 0;
}
void a() {
b();
}
void b() {
c();
}
void c() {
}
main() a() b() c()
- main()
|
+> - a()
. |
. +> - b()
. . |
. . +> - c()
. . . |
. . + <- return from c()
. . |
. + <- return from b()
. |
+ <- return from a()
|
- return from main()
函数的调用有完美的嵌套关系——调用者的生命期总是长于被调用者的生命期,并且后者在前者的之内。这样,被调用者的局部信息所占空间的分配总是后于调用者的(后入),而其释放则总是先于调用者的(先出),所以正好可以满足栈的LIFO顺序,选用栈这种数据结构来实现调用栈是一种很自然的选择。
参考:-- https://www.zhihu.com/question/34499262
(3)递归转成非递归调用
举例:二叉树的非递归遍历
扫描二维码关注公众号,回复:
8749391 查看本文章
待完成:…