栈 与 递归 不得不说的“故事”

基本思想

  • 把规模较大的一个问题,分解成规模较小的多个子问题去解决,而每一个子问题又可以继续拆分成多个更小的子问题。
  • 递归解决的是有依赖关系的多个问题:必须先解决最小子问题,在层层递进的方式解决当前问题

广义递归

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 查看本文章

待完成:…

发布了222 篇原创文章 · 获赞 54 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/weixin_39966065/article/details/104037329