再回递归调用

来源:互联网 发布:sql数据库软件 编辑:程序博客网 时间:2024/05/21 22:33

<pre name="code" class="cpp">递归调用是困扰我很久的问题,作为一名“新手”,我一直不知如何理解递归调用,即便是看了书上写的“当函数直接或间接地调用自己时,便发生了递归”。恰好今天重新看了书,还有看了一篇博客,令我大受启发。(博客地址:http://blog.csdn.net/vagrxie/article/details/8470798

1.首先来记录一下书上的解释。

如果递归函数调用自己,则被调用的函数也将调用自己,这将无线循环下去,除非代码中包含终止调用链的内容(语句)。 通常的方法将递归调用放在if语句中。例如,void类型的递归函数recurs()代码如下:

void recurs(arguments){      statements1;      if (test)         recurs(arguments);      statements2;}
test最终将为false,调用链将断开。

只要if语句为true,每个recurs()调用都将执行statement1,然后再调用recurs(),而不会执行statement2。当if语句为false时,当前调用将执行statement2。当前调用结束后,程序控制权将返回给调用它的recurs(),而该recurs()将执行其statement2部分,然后结束,并将控制权返回给前一个调用,以此类推。因此,如果recurs()进行了5次递归调用,则第一个statement1部分将按函数调用的顺序执行5次,然后statement2部分将以与函数调用相反的顺序执行5次。进入了5层递归后,程序将沿进入的路径返回。


2.再看看那篇博客的解释。

例题:用递归调用来求斐波那契数列

程序:

#include <iostream>using namespace std;int main(){int f(int);cout<<f(5)<<endl;return 0;}int f(int n){if (n==1||n==2) return 1;else return f(n-1)+f(n-2);}
分析:

n=5

f(5)=f(4)+f(3)

f(4)=f(3)+f(2)

f(3)=f(2)+f(1)
而f(2)=f(1)=1,所以

f(3)=1+1=2

f(4)=2+1=3

f(5)=3+2=5

我们怎么判断这个阶乘的递归计算是否是正确的呢? 先别说测试, 我说我们读代码的时候怎么判断呢?
回溯的思考方式是这么验证的, 比如当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提到一种方法, 给我很大启发, 该方法如下:

  1. 当n=0, 1的时候, 结果正确.
  2. 假设函数对于n是正确的, 函数对n+1结果也正确.
    如果这两点是成立的,我们知道这个函数对于所有可能的n都是正确的。

这种方法很像数学归纳法, 也是递归正确的思考方式, 事实上, 阶乘的递归表达方式就是1!=1,n!=(n-1)!×n(见wiki). 当程序实现符合算法描述的时候, 程序自然对了, 假如还不对, 那是算法本身错了…… 相对来说, n,n+1的情况为通用情况, 虽然比较复杂, 但是还能理解, 最重要的, 也是最容易被新手忽略的问题在于第1点, 也就是基本用例(base case)要对. 比如, 上例中, 我们去掉if n <= 1的判断后, 代码会进入死循环, 永远不会结束.

上面讲了怎么理解递归是正确的, 同时可以看到在有递归算法描述后, 其实程序很容易写, 那么最关键的问题就是, 我们怎么找到一个问题的递归算法呢?
Paul Graham提到, 你只需要做两件事情:

  1. 你必须要示范如何解决问题的一般情况, 通过将问题切分成有限小并更小的子问题.
  2. 你必须要示范如何通过有限的步骤, 来解决最小的问题(基本用例).
    如果这两件事完成了, 那问题就解决了. 因为递归每次都将问题变得更小, 而一个有限的问题终究会被解决的, 而最小的问题仅需几个有限的步骤就能解决.

这个过程还是数学归纳法的方法, 只不过和上面提到的一个是验证, 一个是证明.





0 0
原创粉丝点击