【数据结构学习记录7】——栈与递归(其实更多的是解析递归)

一.递归

1.概念

什么是递归,就是高中数学上我们学的那个,递推式。当然,如果把一个递推公式用函数来表示,那么我们可以叫这个函数递归函数递归函数是一个直接调用自己或通过一系列的语句简介地调用自己的函数,称作递归函数。

比如我们的阶乘函数:
F a c t ( n ) = { 1 , n = 0 n ⋅ F a c t ( n − 1 ) , n > 0 Fact(n)=\left\{ \begin{aligned} 1,n=0\\ n\cdot Fact(n-1),n>0\\ \end{aligned} \right. Fact(n)={1,n=0nFact(n1),n>0
亦或者我们比较熟悉的斐波那契数列:
F i b ( n ) = { 0 , n = 0 1 , n = 1 F i b ( n − 1 ) ⋅ F i b ( n − 2 ) , n > 1 Fib(n)=\left\{ \begin{aligned} 0,n=0\\ 1,n=1\\ Fib(n-1)\cdot Fib(n-2),n>1\\ \end{aligned} \right. Fib(n)=0,n=01,n=1Fib(n1)Fib(n2),n>1

2.实现

我们可以把那些常数项,叫做递归边界,比如阶乘的f(n)=1,n=0或者斐波那契数列的f(n)=0,n=0 ,f(n)=1,n=1

那我们写段实现斐波那契数列的递归代码:

int Fib(int n)
{
    if (n == 0) return 0;
    else if (n == 1) return 1;
    else
    {
        return Fib(n-1) + Fib(n-2);
    }
}

int main()
{
    printf("%d", Fib(10));
    return 0;
}

只要我们能写出递归的函数表达式,就可以写出它的递归函数。个人认为:递归边界是最重要的。如果递归边界没有处理好,那么递归的可能永远持续下去,然后爆栈而死。

3.较难的递归分析——汉诺塔

在这里插入图片描述

汉诺塔的游戏规则就是:把A中的盘子移动到C盘子上,大盘子不能放在小盘子上。

对于这个我们多试几次:

  1. n=1

    第1次 1号盘 A---->C

  2. n=2

    第1次 1号盘 A---->B

    第2次 2号盘 A---->C

    第3次 1号盘 B---->C

  3. n=3

    第1次 1号盘 A---->C

    第2次 2号盘 A---->B

    第3次 1号盘 C---->B

    第4次 3号盘 A---->C

    第5次 1号盘 B---->A

    第6次 2号盘 B---->C

    第7次 1号盘 A---->C

如果我们用整体法的思想来看,如果把一堆盘子从A移动到C,那么我们可以把A柱子上的盘子看成两个整体,最下面的盘子和它上面的所有盘子。那么我们的过程就是:

  1. 上面所有的盘子从A到B
  2. 最下面的盘子从A到C
  3. 上面所有的盘子从B到C

那么我们如何把最上面的盘子移动到B和C呢?那就把这部分盘子看成两部分,最下面一张盘子和上面所有的盘子。

如果移动到B,那么C就是我们的辅助塔(因为游戏是从A移动到C,所以B是辅助塔),所以我们就对这上面的部分执行“A到B的汉诺塔操作”。

反之,从B移动到C的话,A就是辅助塔,所以我们执行的是"B到C的焊诺特操作"

开始套娃。直到最后我们只有一个盘子,那么狠显然,它执行“A到C的汉诺塔操作”且只有一块盘子,那就把它移动到C。

那么我们的递归表达式就出来了:

递归边界: n=1时 把1号盘子 A→C

递归内容:①把n-1号盘子 A→B把n号盘子 A→C把n-1号盘子 B→C

好了,代码实现一下:

void hano(int n, char a, char b, char c)    // hano(n=有几个碟子,a=主塔,b=辅助塔,c=目标塔) 
{
    if (n == 1) printf("No.%d  %c -> %c\n", 1, a, c);
    else
    {
        hano(n - 1, a, c, b);   // 这里是A to B的操作,C是辅助塔
        printf("No.%d  %c -> %c\n", n, a, c);
        hano(n - 1, b, a, c); // 这里是B to C的操作,A是辅助塔
    }
    
}

int main()
{
    hano(3, 'A', 'B', 'C'); // 这里是A to C的操作
    return 0;
}

二.栈与递归的关系

在高级语言中,如果需要调用(这是动词)函数,那么调用(名词)函数被调用函数之间的链接与信息交换需要通过内部的栈进行。

在运行被调用函数之前,系统会完成三件事:

  1. 把所有的实参、返回地址等信息传递给被调用函数保存。
  2. 为被调用函数的局部变量分配储存区
  3. 将控制转移到被调用函数的入口。

然后在被调用函数返回调用函数之前,我们也要完成三件事:

  1. 保存被调函数的计算结果
  2. 释放被调用函数的数据区
  3. 依照被调用函数保存的返回地址将控制转移到调用函数。

假设我们像递归这样成嵌套的调用时,那么先调用的函数肯定要最后返回,所以在编译器的内部,是通过栈的方式为我们保存这些关系与信息。

所以我们的递归之间的信息传递需要使用到栈,幸好编译器会帮我们管理递归工作栈,所以我们只需要好好考虑递归边界和递归的内容就可以啦!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

康娜喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值