详细探讨递归
2015-09-12 18:19
344 查看
神奇而又巧妙地递归
递归调用:
定义: 函数本身调用自己本身。
那么不是无限循环了吗? 当然不是! 因为一定得有一个递归链在变化, 并且一定会断。(不然就会陷入无限循环,直至内存用尽) (当然你的操作系统不会允许这样SB的事情发生,它在一定程度下就会中断该程序的运行!)。
小弱曾经学递归时, 翻过很多的书籍, 然而大多数的语言书籍(特别是国内的作者) 都只是用一个阶乘的例子来讲解递归, 或者 用斐波那契数列来引入递归。 但是它们并不能很好的完整的反映递归的用法。 更不用说原理啦! (包括谭浩强的书, 和C++ primer) 。
请看下面的示例:
递归调用有一个很有趣的现象发生, 即: 只要 if 语句为true, 每个recurs() 调用都将执行statments1, 然后再递归调用recurs()而不会执行statements 2. 当 if 语句为false 时, 当前调用将执行 statements2。 当前调用结束后, 程序控制权将会返回给调用它的recurs(), 而该recurs()将执行其statement2部分, 然后结束, 并将控制权返回给前一个调用, 以此类推。 因此如果 recurs()进行了三次调用, 则第一个statments1部分将按函数调用的顺序执行 3 次, 然后 statments 2 部分 会以相反的顺序执行 3 次。 看下面的例子。
运行一下上面的程序, 看一下效果!
为什么会发生这种情况呢? 这是因为递归是借助于一种特殊的结构实现的------调用栈。
执行constdown(4) 输出 “Counting down,,, 4 ”, 由于(4>0) 成立, 执行constdown(4-1) 然后把未知执行的部分压入调用栈中,(或者说在这里设置了断点)
然后一直执行到 n == 0; 此时输出 “Counting down,,, 0” 用于if(0>0) 不成立, 于是执行输出 “0: Kaoom!” 它把控制权交给它的上一个函数 依次类推,,,。 直至栈空!!
那么这样就可以在不用数组的情况下, 把十进制传化成二进制啦。 由于经过除以2 取余法, 可以求出各个位的数字, 但是顺序是反的。 一种方法就是用数组把各个位的数都存起来, 然后逆序输出就行啦! 第二种方法就是 用递归的方法, 因为递归的第二个部分就是逆着输出的。
用递归反转字符串:
简单分析: 首先是第一个字符与最后一个交换, 然后是第二个和倒数第二个,,,,
强调: 以上内容只是递归的语法讲解, 以及简单小练习。 递归函数有许多巧妙地应用, 如DFS, BFS, 二叉树, 四叉树遍历等等,以及工程中的问题, 有着广泛的应用。
旁注:
递归的实现依赖于 “栈机制”。 在机器代码层面, 不同处理器 对 “栈机制”的实现方式不同。 原先的 intel 处理器 是使用两个指针,分别叫做 栈 和 帧 来实现“栈机制”的。 帧指针指示保存调用函数的信息, 如: 调用函数的局部变量, 状态码, 返回地址, 以及一些寄存器里面的值。 栈指针用来保存被调用函数的信息。 其中这里有一个潜在的危险。 如果被调用函数实际所使用的内存大于栈指针所分配的, 那么将覆盖掉帧指针的空间。 从而引起一个严重的危险 --- 缓冲期溢出。这个漏洞, 曾经是无数病毒的温床, 如: 蠕虫, 木马等。 现在计算机系统利用随机的内存分配形式,来为程序分配内存, 这个漏洞得到了缓解。 但是仍然不能阻挡真正牛叉的hackers。 现在的 interl处理器, 由于寄存器数目的增加, 为了加快数据的访问速度, 已经取消了帧指针。 取而代之的是, 把调用函数需要保存的信息存储在寄存器中。
栈机制的实际的内部实现是十分的巧妙和复杂的, 但幸运的是, 我们只需要了解它的逻辑机理即可!
推荐阅读: 《深入理解计算机系统》
递归调用:
定义: 函数本身调用自己本身。
那么不是无限循环了吗? 当然不是! 因为一定得有一个递归链在变化, 并且一定会断。(不然就会陷入无限循环,直至内存用尽) (当然你的操作系统不会允许这样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处理器, 由于寄存器数目的增加, 为了加快数据的访问速度, 已经取消了帧指针。 取而代之的是, 把调用函数需要保存的信息存储在寄存器中。
栈机制的实际的内部实现是十分的巧妙和复杂的, 但幸运的是, 我们只需要了解它的逻辑机理即可!
推荐阅读: 《深入理解计算机系统》
相关文章推荐
- OPENSSL_Uplink(0F5D2000,08): no OPENSSL_Applink
- 机器学习温和指南
- U3D HTTP 最好用的插件BestHttp
- eclipse code format の google code format
- TED笔记-开源建筑
- 【IOS 开发学习总结-OC-7.1】C 语言特性——函数
- 错误处理
- UIStatusBarStyle的类型改变
- 邮件内容超链接
- SQLite数据存储
- c++遇到个简单而费时的小语法问题
- sql 排名函数 rank() , row_number() , dense_rank() over
- 上机实验 文件读取与异常处理
- iOS scrollView去掉系统自适高度
- PM与敏捷
- 深入解读Quartz的原理(job-jobDetail-Trigger-scheduler)
- 基于Appium 的 UI for Mac自动化测试环境配置教程
- Android图形编程基本概念
- Unity开发者的C#内存管理
- COCI CONTEST #1 18.10.2014 T4 MAFIJA