详细探讨递归

                                                          

                                                                                               神奇而又巧妙地递归

 

 

递归调用:

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

那么不是无限循环了吗? 当然不是! 因为一定得有一个递归链在变化, 并且一定会断。(不然就会陷入无限循环,直至内存用尽) (当然你的操作系统不会允许这样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!\n";
}

执行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处理器, 由于寄存器数目的增加, 为了加快数据的访问速度, 已经取消了帧指针。 取而代之的是, 把调用函数需要保存的信息存储在寄存器中。

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

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

 

 

 

 

posted @ 2015-09-12 18:19  草滩小恪  阅读(509)  评论(0编辑  收藏  举报