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,两者合在一起理想的状态应该是
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的排列的问题,我们可以将它分解成计算(n−1)! ,(n−1)! 继续分解(n−1−1)!
最后必定会得出一个最小的问题: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时的代码执行流程
- 程序main()调用①recur(i=0, n=1), 打印 B>,说明一下:这里用=代表相等的意思。
- 第一次进入的for循环记作①for( j=0,i=0, n=1),打印 I> , 开始调用 ②recur(i=1),n值是不变的。
- ②recur(i=1) 压栈,执行, 打印 B>, 然后进入 ②for (j=1,i=1) ,打印 I>
- 调用③recur(i=2),打印B>, 到③for (j=2,i=2),空操作,从③recur(i=2)返回
- ②for (j=1,i=1) 继续下面的print R>, 然后②for (j=2) 空操作,从②recur(i=1)返回
- ②recur(i=1) 返回①for(j=0,i=0),print R>,然后①for(j=1,i=0),print I,调用④recur(i=1)
- ④recur(i=1)入栈,print B>, 然后④for(j=i=1), print I, 调用⑤recur(i=2)
- ⑤recur(i=2)入栈,print B, 然后⑤for(j=i=2)空操作,从⑤recur(i=2)返回,print R
- ④for(j=i=2)空操作,从④recur(i=1)返回,print R,①for(j=2,i=0),空操作,
- 从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,我想要非人才能分析清晰。