zoukankan      html  css  js  c++  java
  • 详细探讨递归

                                                              

                                                                                                   神奇而又巧妙地递归

    递归调用:

    定义: 函数本身调用自己本身。

    那么不是无限循环了吗? 当然不是! 因为一定得有一个递归链在变化, 并且一定会断。(不然就会陷入无限循环,直至内存用尽) (当然你的操作系统不会允许这样SB的事情发生,它在一定程度下就会中断该程序的运行!)。

     

    小弱曾经学递归时, 翻过很多的书籍, 然而大多数的语言书籍(特别是国内的作者) 都只是用一个阶乘的例子来讲解递归, 或者 用斐波那契数列来引入递归。 但是它们并不能很好的完整的反映递归的用法。 更不用说原理啦! (包括谭浩强的书, 和C++ primer) 。

    请看下面的示例:

    void recurs(argumentlist)
    {
        statments1;
        if(test)
            recurs(arguments);
        statents2;
    }

    递归调用有一个很有趣的现象发生, 即: 只要 if 语句为true, 每个recurs() 调用都将执行statments1, 然后再递归调用recurs()而不会执行statements 2. 当 if 语句为false 时, 当前调用将执行 statements2。  当前调用结束后, 程序控制权将会返回给调用它的recurs(), 而该recurs()将执行其statement2部分, 然后结束, 并将控制权返回给前一个调用, 以此类推。 因此如果 recurs()进行了三次调用, 则第一个statments1部分将按函数调用的顺序执行 3 次, 然后 statments 2 部分  会以相反的顺序执行 3 次。 看下面的例子。

    运行一下上面的程序, 看一下效果!

    为什么会发生这种情况呢? 这是因为递归是借助于一种特殊的结构实现的------调用栈。

    #include<iostream>
    void constdown(int n);
    
    int main()
    {
        constdown(4);
        return 0;
    }
    
    void constdown(int n)
    {
        using namespace std;
        cout<< "Counting down ... "<< n << endl;
        if(n > 0)
            constdown(n-1);
        cout << n << ": Kaboom!
    ";
    }

    执行constdown(4) 输出 “Counting down,,, 4 ”,  由于(4>0) 成立, 执行constdown(4-1)  然后把未知执行的部分压入调用栈中,(或者说在这里设置了断点)

    然后一直执行到 n == 0; 此时输出 “Counting down,,, 0” 用于if(0>0) 不成立, 于是执行输出 “0: Kaoom!” 它把控制权交给它的上一个函数 依次类推,,,。 直至栈空!!

     

    那么这样就可以在不用数组的情况下, 把十进制传化成二进制啦。 由于经过除以2 取余法, 可以求出各个位的数字, 但是顺序是反的。 一种方法就是用数组把各个位的数都存起来, 然后逆序输出就行啦! 第二种方法就是 用递归的方法, 因为递归的第二个部分就是逆着输出的。 

    #include<cstdio>
    
    void tobit(int n);
    int main()
    {
        tobit(255);
        getchar();
        return 0;
    }
    
    void tobit(int n)
    {
        int i = n%2;
        if(n>1) tobit(n/2);
        putchar( i ? '1': '0');
    }

     

    用递归反转字符串:

    简单分析: 首先是第一个字符与最后一个交换, 然后是第二个和倒数第二个,,,,

    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    
    void reverse(char *str, int len)
    {
            swap(str[0], str[len-1]);
        if(len>1)
            reverse(str+1, len-2);
    }
    
    int main()
    {
        char str[] = "I love you , do you know? ";
        reverse(str, strlen(str));
        cout<<str<<endl;
        return 0;
    }

     强调: 以上内容只是递归的语法讲解, 以及简单小练习。 递归函数有许多巧妙地应用, 如DFS, BFS, 二叉树, 四叉树遍历等等,以及工程中的问题, 有着广泛的应用。

    旁注:

      递归的实现依赖于 “栈机制”。 在机器代码层面, 不同处理器 对 “栈机制”的实现方式不同。 原先的 intel 处理器 是使用两个指针,分别叫做 栈 和 帧 来实现“栈机制”的。 帧指针指示保存调用函数的信息, 如: 调用函数的局部变量, 状态码, 返回地址, 以及一些寄存器里面的值。 栈指针用来保存被调用函数的信息。 其中这里有一个潜在的危险。 如果被调用函数实际所使用的内存大于栈指针所分配的, 那么将覆盖掉帧指针的空间。 从而引起一个严重的危险 --- 缓冲期溢出。这个漏洞, 曾经是无数病毒的温床, 如: 蠕虫, 木马等。 现在计算机系统利用随机的内存分配形式,来为程序分配内存, 这个漏洞得到了缓解。 但是仍然不能阻挡真正牛叉的hackers。 现在的 interl处理器, 由于寄存器数目的增加, 为了加快数据的访问速度, 已经取消了帧指针。 取而代之的是, 把调用函数需要保存的信息存储在寄存器中。

          栈机制的实际的内部实现是十分的巧妙和复杂的, 但幸运的是, 我们只需要了解它的逻辑机理即可!

      推荐阅读: 《深入理解计算机系统》

     

     

     

  • 相关阅读:
    P4781 【模板】拉格朗日插值
    P1306 斐波那契公约数
    P1154 奶牛分厩
    P1028 数的计算
    P1445 [Violet]樱花
    2020 Multi-University Training Contest 4
    Codeforces Round #658 (Div. 2) D
    2020牛客暑期多校训练营(第八场) K
    Codeforces Round #659 (Div. 2)
    #10106. 「一本通 3.7 例 2」单词游戏
  • 原文地址:https://www.cnblogs.com/acm1314/p/4803283.html
Copyright © 2011-2022 走看看