深入理解递归
来源:互联网 发布:仙女网络语言什么意思 编辑:程序博客网 时间:2024/06/09 20:38
内容会持续更新,有错误的地方欢迎指正,谢谢!
什么是迭代和递归
迭代的是人,递归的是神。 –L. Peter Deutsch
简单的定义: 当函数直接或者间接调用自己时,则发生了递归。但递归并不直观, 也不符合我们的思维习惯,所以,我们更加容易理解迭代。
例子:计算一个字符串的长度—迭代实现
//迭代的描述: 从第一个字符开始,只要字符不为0,则+1。int Length(const char* str){ int size=0; while(*str) { ++size; ++str; } return size;}
计算一个字符串的长度—递归实现
//递归的描述: 当前字符串的长度等于剩下的字符串长度+X,X为递归函数已执行的次数。int Length(const char* str){ if(*str==0) return 0; return Length(++str)+1;}
递归的原理
例子:
n的阶乘
int func(int n){ if(n<=1) { return 1; } return n*func(--n);}
Paul Graham提到一种方法, 算是递归正确的思考方式,该方法如下:
1. 当n=0, 1的时候,结果正确。
2. 假设函数对于n是正确的,函数对n+1结果也正确。
如果这两点是成立的,我们知道这个函数对于所有可能的n都是正确的。
其实就是数学归纳法。最重要的是第1点,如果我们去掉if(n<=1)
这个基本用例后, 代码会进入死循环,永远不会结束。
递归的实现
既然递归比迭代更难理解,为啥我们还要用递归呢?因为有些问题用递归来解决要省时省力很多!
例子:斐波拉契数列:f(0)=0 f(1)=1 f(n)=f(n-1)+f(n-2)
迭代实现:
int Fibo(int n){ int f2=0 , f0=0 , f1=1; for(int i=0;i<n-1;++i) { f2=f0+f1; f0=f1; f1=f2; } return f2;}
递归实现:
int Fibo(int n){ if(n==0) return 0; if(n==1) return 1; return Fibo(n-1)+Fibo(n-2);}
递归来实现,再简单不过了,但时间复杂度很高~
有上面的例子可得,有递归算法描述后,程序很容易写,那么关键问题就是怎么得到递归算法描述?
Paul Graham方法,只需要做两件事情:
- 如何解决问题的一般情况, 通过将问题切分成有限小并更小的子问题。
- 如何通过有限的步骤, 来解决最小的问题(基本用例)。
如果这两件事完成了, 那问题就解决了。这个过程还是数学归纳法的思想。
Paul Graham方法的应用
汉诺塔问题:
一般情况:
当有N个圆盘在A杠上,我们已经找到办法将其移到C杠上了,我们怎么移动N+1个圆盘到C杠上呢?很简单,我们首先用将N个圆盘移动到C杠上的方法将N个圆盘都移动到B杠上,然后再把第N+1个圆盘(最后一个)移动到C杠上,再用同样的方法将在B杠上的N个圆盘移动到C杠上。问题解决。
最小的问题(基本用例):
当有1个圆盘在A上, 我们直接把圆盘移动到C上即可。
算法描述大概就是上面这样,其实也可以看作思维的过程。
A为存放盘子的塔,B为辅助塔,C为目标塔。
算法分为三步:
一、将A上n-1个盘子全部放到B塔上
二、将A上剩下的一个盘子放到C塔上
三、将B塔上的n-1个盘子全部放到C塔上
注:不需要考虑如何移动n-1个盘子,考虑得越多,越谜,还是把人生活得快乐些吧~
void HanoTower(int n,char from,char temp,char to){ if(n==1) cout<<"From "<<from<<" To "<<to<<endl; else { HanoTower(n-1,from,to,temp); HanoTower(1,from,temp,to); HanoTower(n-1,temp,from,to); }}int main(){ HanoTower(3,'A','B','C'); return 0;}
当n=3时的输出:
From A To C
From A To B
From C To B
From A To C
From B To A
From B To C
From A To C
看起来这么复杂的问题,用递归这么容易,没有想到吧。要是用迭代来解决这个问题呢?你试试吧,试完就能体会到递归的好处了。
写递归函数的注意项
除了使用Paul Graham方法,还有几点需要注意:
1. 要分析清楚满足递归的条件,并全部列出:
写之前就一定要想清楚什么时候这个函数会调用自己,为了防止疏漏条件,最好把所有满足递归的条件都列在纸上或者文档上,一定要尽可能的全面。因为我们经常容易漏掉某一种满足条件,那么结果自然就会不正确。
2. 要分析不满足条件时的处理方式:
就是正确的情景考虑到了后还要考虑错误的情景。
3. 要分析递归函数的返回值:
如果递归函数有返回值,那么每执行完一次递归函数后,如何接收、处理该递归函数的返回值。
4. 写完递归函数后一定要进行单元测试:
可以将循环的次数和递归后的结果打印出来,看看打印后的结果是否符合自己的预期,如果某一递归出现问题,可以根据循环次数的记录在调试的时候直接定位,这样效率会高很多。测试的时候一定要涉及到所有满足递归的条件,每一条件分支都要检查一遍。
递归总结
优点:结构清晰,可读性强,为设计算法、调试程序带来很大方便。
缺点:在递归调用中,系统为每一层的返回点、局部量等开辟了栈来存储,易造成栈溢出。
递归算法一般用于解决三类问题:
1. 数据的定义是按递归定义的。(Fibonacci函数)
2. 问题解法按递归算法实现。(汉诺塔问题)
3. 数据的结构形式是按递归定义的。(树的遍历,图的搜索)
递归算法可以分为三种类型:基于递归策略的分治算法、基于递归策略的自顶向下的动态规划算法、基于递归策略的回溯算法。
a.基于递归策略的分治法(先拆,再解,后合),当问题能分解为独立的子问题,就可以使用分治法。
举例:
1. 阶乘
2. 斐波拉契数列(又叫 爬楼梯问题)
3. 汉诺塔问题
4. 矩阵乘法Strassen’s算法
5. 最近点对问题
b.基于递归策略的动态规划(自顶向下类型):
当问题不能分解为独立的子问题,却又符合最优化原理时,就可以使用动态规划法。
举例:
1. 装配线排程问题
2. 最长共同子序列问题
3. 背包问题
c.基于递归策略的回溯:
当问题找不到数据间的相互关系、也不能将问题分解为独立的子问题,就只有把全部解都列出来,才能推断出问题的解。遍历问题各个可能解的通路,当发现此路不通时,回溯到上一步继续尝试别的通路。
参考
【1】写递归函数的正确思维方法
http://blog.csdn.net/vagrxie/article/details/8470798
【2】如何编写递归程序(分治法)
http://blog.csdn.net/xgf415/article/details/52026961
【3】算法策略的总结
http://www.cnblogs.com/steven_oyj/archive/2010/05/22/1741482.html
- 深入理解递归
- 深入理解递归函数
- 深入理解递归算法
- 深入理解递归
- 深入理解递归算法
- 3. 深入理解递归
- 深入理解递归
- 汉诺塔-递归算法深入理解
- 深入理解递归函数的调用过程
- 深入理解递归函数的调用过程
- 深入理解递归函数的调用过程
- 深入理解递归:全排列问题
- 递归三部曲之深入理解全排列
- 深入理解递归函数的调用过程
- 深入理解递归函数的调用过程
- 深入理解递归以及汉诺塔问题[数据结构]
- 深入理解递归函数的调用过程
- 递归关于内存的深入理解
- EOJ Monthly 2017.12 题解 3449. 唐纳德和他的数学老师
- UE4使用心得
- c语言第三节课
- Linux 进程PK线程;互斥量PK信号量
- 粒子系统
- 深入理解递归
- ArchLinux2017.12.01安装for笔记本 (多系统实战)
- 多线程编程
- 进制数转换
- eclipse常用快捷键
- 线渲染器
- 学Python(1)—循环
- HTTP 工作原理及HTTP请求方法
- C语言·关于栈帧