如何理解递归
来源:互联网 发布:微信公众号源码下载 编辑:程序博客网 时间:2024/04/26 22:31
http://www.jtianling.com/the-right-way-to-write-recursive-function.html
我们怎么判断这个阶乘的递归计算是否是正确的呢? 先别说测试, 我说我们读代码的时候怎么判断呢? 回溯的思考方式是这么验证的, 比如当n = 4时, 那么factoria(4)
等于4 * factoria(3)
, 而factoria(3)
等于3 * factoria(2)
, factoria(2)
等于2 * factoria(1)
, 等于2 * 1
, 所以factoria(4)
等于4 * 3 * 2 * 1
. 这个结果正好等于阶乘4的迭代定义. 用回溯的方式思考虽然可以验证当n = 某个较小数值是否正确, 但是其实无益于理解. Paul Graham提到一种方法, 给我很大启发, 该方法如下:
- 当n=0, 1的时候, 结果正确.
- 假设函数对于n是正确的, 函数对n+1结果也正确. 如果这两点是成立的,我们知道这个函数对于所有可能的n都是正确的.
这种方法很像数学归纳法, 也是递归正确的思考方式, 事实上, 阶乘的递归表达方式就是1!=1,n!=(n-1)!×n
(见wiki). 当程序实现符合算法描述的时候, 程序自然对了, 假如还不对, 那是算法本身错了…… 相对来说, n,n+1的情况为通用情况, 虽然比较复杂, 但是还能理解, 最重要的, 也是最容易被新手忽略的问题在于第1点, 也就是基本用例(base case)要对. 比如, 上例中, 我们去掉if n <= 1
的判断后, 代码会进入死循环, 永远不会结束.
使用递归
既然递归比迭代要难以理解, 为啥我们还需要递归呢? 从上面的例子来看, 自然意义不大, 但是很多东西的确用递归思维会更加简单……
经典的例子就是斐波那契数列, 在数学上, 斐波那契数列就是用递归来定义的:
F0 = 0
F1 = 1
Fn = Fn - 1 + Fn - 2
有了递归的算法, 用程序实现实在再简单不过了:
def fibonacci(n)if n == 0 thenreturn 0elsif n == 1 thenreturn 1elsereturn fibonacci(n - 1) + fibonacci(n - 2)endend
改为用迭代实现呢? 你可以试试. 上面讲了怎么理解递归是正确的, 同时可以看到在有递归算法描述后, 其实程序很容易写, 那么最关键的问题就是, 我们怎么找到一个问题的递归算法呢? Paul Graham提到, 你只需要做两件事情:
- 你必须要示范如何解决问题的一般情况, 通过将问题切分成有限小并更小的子问题.
- 你必须要示范如何通过有限的步骤, 来解决最小的问题(基本用例).
如果这两件事完成了, 那问题就解决了. 因为递归每次都将问题变得更小, 而一个有限的问题终究会被解决的, 而最小的问题仅需几个有限的步骤就能解决.
这个过程还是数学归纳法的方法, 只不过和上面提到的一个是验证, 一个是证明. 现在我们用这个方法来寻找汉诺塔这个游戏的解决方法.(这其实是数学家发明的游戏)
有三根杆子A,B,C。A杆上有N个(N>1)穿孔圆盘,盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至C杆: 1.每次只能移动一个圆盘. 2.大盘不能叠在小盘上面.
这个游戏在只有3个盘的时候玩起来较为简单, 盘越多, 就越难, 玩进去后, 你就会进入一种不停的通过回溯来推导下一步该干什么的状态, 这是比较难的. 我记得第一次碰到这个游戏好像是在大航海时代某一代游戏里面, 当时就觉得挺有意思的. 推荐大家都实际的玩一下这个游戏, 试试你脑袋能想清楚几个盘的情况. 现在我们来应用Paul Graham的方法思考这个游戏.
一般情况: 当有N个圆盘在A上, 我们已经找到办法将其移到C杠上了, 我们怎么移动N+1个圆盘到C杠上呢? 很简单, 我们首先用将N个圆盘移动到C上的方法将N个圆盘都移动到B上, 然后再把第N+1个圆盘(最后一个)移动到C上, 再用同样的方法将在B杠上的N个圆盘移动到C上. 问题解决. 基本用例: 当有1个圆盘在A上, 我们直接把圆盘移动到C上即可. 算法描述大概就是上面这样了, 其实也可以看作思维的过程, 相对来说还是比较自然的. 下面是Ruby解:
def hanoi(n, from, to, other)if n == 1 thenputs from + ' -> ' + toelsehanoi(n-1, from, other, to)hanoi(1, from, to, other)hanoi(n-1, other, to, from)endend
当n=3时的输出: > A -> C > A -> B > C -> B > A -> C > B -> A > B -> C > A -> C
上述代码中, from, to, other的作用其实也就是提供一个杆子的替代符, 在n=1时, 其实也就相当于直接移动. 看起来这么复杂的问题, 其实用递归这么容易, 没有想到吧. 要是想用迭代来解决这个问题呢? 还是你自己试试吧, 你试的越多, 就能越体会到递归的好处.
- 如何理解递归
- 如何理解递归
- 到底如何理解递归?
- 如何通俗理解递归
- 如何理解递归?
- 如何通俗的理解递归
- 如何理解汉诺塔的递归?
- 如何去理解递归,想到递归,运用递归
- 关于如何理解递归的两则小例子
- 递归算法详细分析(如何更好的理解递归)
- 递归算法--如何一步一步理解递归(1)
- 递归算法--如何一步一步理解递归(2)--全排列
- 理解递归
- 理解递归!!
- 理解递归
- 理解 递归
- 递归理解
- 理解递归
- 【一步步学OpenGL 8】 -《缩放变换》
- 【Java TCP/IP Socket】TCP Socket通信中由read返回值造成的的死锁问题(含代码)
- 【POJ2385】Apple Catching(简单DP)
- 平台总线(二)
- Android面试基础
- 如何理解递归
- android中activuty的简单总结
- Linux - C预习内容(七)
- java中常用的加密方式
- 学习python写网络爬虫(一)
- 从零开始学习Nodejs(一)
- 【Java TCP/IP Socket】Java NIO Socket VS 标准IO Socket
- JZOJ4559 【NOI2016模拟6.23】水平线上的肮脏交易和卑鄙勾当 转换模型后CDQ分治
- 设置的mysql密码无效的问题