详细探讨递归
神奇而又巧妙地递归
递归调用:
定义: 函数本身调用自己本身。
那么不是无限循环了吗? 当然不是! 因为一定得有一个递归链在变化, 并且一定会断。(不然就会陷入无限循环,直至内存用尽) (当然你的操作系统不会允许这样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处理器, 由于寄存器数目的增加, 为了加快数据的访问速度, 已经取消了帧指针。 取而代之的是, 把调用函数需要保存的信息存储在寄存器中。
栈机制的实际的内部实现是十分的巧妙和复杂的, 但幸运的是, 我们只需要了解它的逻辑机理即可!
推荐阅读: 《深入理解计算机系统》