for循环里面的递归调用探讨

版权声明:本文为博主原创文章,转载时请注明出处,谢谢关注。 https://blog.csdn.net/mikayong/article/details/51706508

for循环里面的递归调用探讨

递归本来要来简化循环问题的,不过程序中往往却有for加递归一起使用的情况。

我们在for里面堪套for,或者for里面的for再堪套for,都能很直观地理解。

当for里面加入了递归,理解的层面就由三维跳到了四维,很难直接观看,要靠无穷的想像力。

先来分析一段小代码

这个代码里,For里面堪套了递归调用,你觉得应该怎样预测它执行的结果呢?

#include <stdio.h>
#include <stdlib.h>

void recur(int, int);
static int count = 0;

int main(int argc, char **argv)
{
    if(argc < 2)
    {
        printf("args need a interger\n");
        return -1;
    }
    int n = atoi(argv[1]);
    recur(0, n);
    printf("  COUNT= %d\n", count);
    return 0;
}   

void recur(int i, int n)
{   
    count++;
    printf("B>");
    for(i; i <= n; i++){
        printf("I>");
        recur(i + 1, n);
        printf("R>");    
    }
}

n次的for循环执行的次数是n,n次的递归调用次数亦是n,两者合在一起理想的状态应该是 n2 次,但因为递归的特性,却大大增加了复杂度。我们来分析一下代码的执行

1.n=1时程序的输出是: B>I>B>I>B>R>R>I>B>R> COUNT=4
2.即recur调用了4次,和我们预期的结果 n2 是相同的
3.n=2时,程序输出是:B>I>B>I>B>I>B>R>R>I>B>R>R>I>B>I>B>R>R>I>B>R> COUNT=8
4.这时候,recur调用次数是 2n ,接着我们再用不同的n测试,也验证了 2n 的正确性
5.为什么要用for循环调用递归呢?那得由递归解决问题的思路说起,递归解题思路是:

(1)存在一个问题
(2)这个问题可以通过分解形成一个小一点的相同的问题
(3)小一点的问题继续可以分解成更小的问题
(4)最后得出一个最小的问题,最小问题不是问题
例如:计算n的排列的问题,我们可以将它分解成计算 n1! (n1)! 继续分解 (n11!
最后必定会得出一个最小的问题: 0!
(5)当问题不只一个时,就是说存在一个大的问题,里面有N个同等的中问题,而这N个同等的中问题都可以按递归思路解题,这时候就需要用for来将N个问题遍历了。
(6)为什么for堪套递归的次数是 2n ?那是因为每一次for都需要调用两次函数,其中for执行一次,for调用递归又需要再执行一次,又根据乘法定理:如果一个过程分成两个阶段,第一阶段有m种可能的结果,并且对于这个阶段的结果,第二阶段都有n种可能的结果,则按指定的次数序完成整个过程一共有mn种方法。这样就可以得出 2n

for调用递归的复杂性不止于此,大家又看看下面的程序,只是要上面的程序里加多一变量,程序的运行复杂度却大大的增加了

#include <stdio.h>
#include <stdlib.h>

void recur(int, int);
static int count = 0;

int main(int argc, char **argv)
{
    if(argc < 2)
    {
        printf("args need a interger\n");
        return -1;
    }
    int n = atoi(argv[1]);
    recur(0, n);
    printf("  COUNT = %d\n", count); 
    return 0;
}

void recur(int i, int n)
{
    int j;
    count++;
    printf("B>");
    for(j = i; j <= n; j++){   /*这里和上面例子不同之处是将i值赋给j*/
        printf("I>");
        recur(i + 1, n);
        printf("R>");

    }
}

当n等于1时,运行的结果的是:

B>I>B>I>B>R>R>I>B>I>B>R>R> COUNT= 5
程序共调用了五次recur函数。

当n等于2时,运行的结果的是:

B>I>B>I>B>I>B>R>R>I>B>I>B>R>R>R>I>B>I>B>I>B>R>R>I>B>I>B>R>R>R>I>B>I>B>I>B>R>R>I>B>I>B>R>R>R> COUNT=16
recur函数的调用一下就升到了16次

当n等于3时,运行的结果的是:

B>I>B>I>B>I>B>I>B>R>R>I>B>I>B>R>R>R>I>B>I>B>I>B>R>R>I>B>I>B>R>R>R>I>B>I>B>I>B>R>R>I>B>I>B>R>R>R>R>I>B>I>B>I>B>I>B>R>R>I>B>I>B>R>R>R>I>B>I>B>I>B>R>R>I>B>I>B>R>R>R>I>B>I>B>I>B>R>R>I>B>I>B>R>R>R>R>I>B>I>B>I>B>I>B>R>R>I>B>I>B>R>R>R>I>B>I>B>I>B>R>R>I>B>I>B>R>R>R>I>B>I>B>I>B>R>R>I>B>I>B>R>R>R>R>I>B>I>B>I>B>I>B>R>R>I>B>I>B>R>R>R>I>B>I>B>I>B>R>R>I>B>I>B>R>R>R>I>B>I>B>I>B>R>R>I>B>I>B>R>R>R>R> COUNT=65
n=3时就更加恐怖了,往下n数值等于10时,等待程序执行结果返回将是一个漫长的过程。

我们来分析一下n=1时的代码执行流程


  1. 程序main()调用①recur(i=0, n=1), 打印 B>,说明一下:这里用=代表相等的意思。
  2. 第一次进入的for循环记作①for( j=0,i=0, n=1),打印 I> , 开始调用 ②recur(i=1),n值是不变的。
  3. ②recur(i=1) 压栈,执行, 打印 B>, 然后进入 ②for (j=1,i=1) ,打印 I>
  4. 调用③recur(i=2),打印B>, 到③for (j=2,i=2),空操作,从③recur(i=2)返回
  5. ②for (j=1,i=1) 继续下面的print R>, 然后②for (j=2) 空操作,从②recur(i=1)返回
  6. ②recur(i=1) 返回①for(j=0,i=0),print R>,然后①for(j=1,i=0),print I,调用④recur(i=1)
  7. ④recur(i=1)入栈,print B>, 然后④for(j=i=1), print I, 调用⑤recur(i=2)
  8. ⑤recur(i=2)入栈,print B, 然后⑤for(j=i=2)空操作,从⑤recur(i=2)返回,print R
  9. ④for(j=i=2)空操作,从④recur(i=1)返回,print R,①for(j=2,i=0),空操作,
  10. 从main()返回,结束。

整理一个流程就是:
main()调用①recur(i=0,)->①for( j=0,i=0)调用 ②recur(i=1) ->②for (j=1,i=1)
调用③recur(i=2)->③for (j=2,i=2)从③recur(i=2)返回②for (j=1,i=1)->②for (j++=2) 从②recur(i=1)
返回①for(j=0,i=0)->①for(j=1,i=0) 调用④recur(i=1)->④for(j=i=1)调用⑤recur(i=2)->
⑤for(j=i=2)从⑤recur(i=2)返回④for(j=i=2)从④recur(i=1)返回①for(j=2,i=0)从main()返回
其中要注意的是当①for时当j++时,i值没有变,依然是0, 所它调用recur时是以i=i+1=2这个值调用的

整个过程复杂程度可见一斑,如果n再增加1,我想要非人才能分析清晰。

猜你喜欢

转载自blog.csdn.net/mikayong/article/details/51706508