递归的运行:从一道“读程序写运行结果”的题目说开来
在今年暑假的算法竞赛练习题中,有这样一个“读程序写运行结果”的题目(第8题),test.c
0001 #include<stdio.h> 0002 #define MAX 100 0003 void solve(char first[], int spos_f, int epos_f, char mid[], int spos_m, int epos_m) 0004 { 0005 int i, root_m; 0006 if(spos_f > epos_f) 0007 return; 0008 for(i = spos_m; i <= epos_m; i++) 0009 if(first[spos_f] == mid[i]) 0010 { 0011 root_m = i; 0012 break; 0013 } 0014 solve(first, spos_f + 1, spos_f + (root_m - spos_m), mid, spos_m, root_m - 1); 0015 solve(first, spos_f + (root_m - spos_m) + 1, epos_f, mid, root_m + 1, epos_m); 0016 printf("%c", first[spos_f]); 0017 } 0018 int main() 0019 { 0020 char first[MAX], mid[MAX]; 0021 int len; 0022 scanf("%d", &len); 0023 scanf("%s", first); 0024 scanf("%s", mid); 0025 solve(first, 0, len - 1, mid , 0, len - 1); 0026 printf("\n"); 0027 return 0; 0028 }
这个函数的调用关系如下:
注意到solve(…)本身进行了递归。
当输入数据为:
7
ABDCEGF
BDAGECF
的时候,输出的结果是:
DBGEFCA
那么,这个结果是如何产生的呢?为了更清楚地看出程序的运行状况,我为程序加入了一些注释和调试代码,把它改成了下面这个样子,test1.c:
0001 #include<stdio.h> 0002 #define MAX 100 0003 void solve(char first[], int spos_f, int epos_f, char mid[], int spos_m, int epos_m) 0004 { 0005 int i, root_m; 0006 0007 printf("\n执行函数:solve(first,%d,%d,mid,%d,%d);\n",spos_f,epos_f,spos_m,epos_m); 0008 printf("传入的first序列: \t"); 0009 for (i=spos_f; i<=epos_f; i=i+1) printf("%c",first[i]); 0010 printf("\n传入的mid序列: \t"); 0011 for (i=spos_m; i<=epos_m; i=i+1) printf("%c",mid[i]); 0012 printf("\n"); 0013 0014 if(spos_f > epos_f) {/*当传入的first区间不存在字符的时候,函数返回*/ 0015 printf("该函数执行完毕:solve(first,%d,%d,mid,%d,%d);\n",spos_f,epos_f,spos_m,epos_m); 0016 return; 0017 } 0018 for(i = spos_m; i <= epos_m; i++) /*从左向右,用root_m在mid中标出第一个和first[spos_f]相同的字符*/ 0019 if(first[spos_f] == mid[i]) 0020 { 0021 root_m = i; 0022 printf("root_m=%d\n",root_m); 0023 break; 0024 } 0025 0026 printf("呼叫下列函数:\n"); 0027 printf("solve: %d\t%d\t%d\t%d\n",spos_f + 1, spos_f + (root_m - spos_m),spos_m, root_m - 1); 0028 printf("solve: %d\t%d\t%d\t%d\n",spos_f + (root_m - spos_m) + 1, epos_f,root_m + 1, epos_m); 0029 0030 solve(first, spos_f + 1, spos_f + (root_m - spos_m), mid, spos_m, root_m - 1); 0031 solve(first, spos_f + (root_m - spos_m) + 1, epos_f, mid, root_m + 1, epos_m); 0032 printf("%c", first[spos_f]); /*当整个过程结束的时候,会打印first区间的开始字符*/ 0033 printf("该函数执行完毕:solve(first,%d,%d,mid,%d,%d);\n",spos_f,epos_f,spos_m,epos_m); 0034 } 0035 int main() 0036 { 0037 char first[MAX], mid[MAX]; 0038 int len; 0039 scanf("%d", &len); 0040 scanf("%s", first); 0041 scanf("%s", mid); 0042 solve(first, 0, len - 1, mid , 0, len - 1); 0043 printf("\n"); 0044 return 0; 0045 }
如果我们使用相同的输入数据,将会得到更加详细的结果,该结果反映了solve(…)函数的具体运行状况。具体结果如下:
执行函数:solve(first,0,6,mid,0,6);
传入的first序列: ABDCEGF
传入的mid序列: BDAGECF
root_m=2
呼叫下列函数:
solve: 1 2 0 1
solve: 3 6 3 6执行函数:solve(first,1,2,mid,0,1);
传入的first序列: BD
传入的mid序列: BD
root_m=0
呼叫下列函数:
solve: 2 1 0 -1
solve: 2 2 1 1执行函数:solve(first,2,1,mid,0,-1);
传入的first序列:
传入的mid序列:
该函数执行完毕:solve(first,2,1,mid,0,-1);执行函数:solve(first,2,2,mid,1,1);
传入的first序列: D
传入的mid序列: D
root_m=1
呼叫下列函数:
solve: 3 2 1 0
solve: 3 2 2 1执行函数:solve(first,3,2,mid,1,0);
传入的first序列:
传入的mid序列:
该函数执行完毕:solve(first,3,2,mid,1,0);执行函数:solve(first,3,2,mid,2,1);
传入的first序列:
传入的mid序列:
该函数执行完毕:solve(first,3,2,mid,2,1);
D该函数执行完毕:solve(first,2,2,mid,1,1);
B该函数执行完毕:solve(first,1,2,mid,0,1);执行函数:solve(first,3,6,mid,3,6);
传入的first序列: CEGF
传入的mid序列: GECF
root_m=5
呼叫下列函数:
solve: 4 5 3 4
solve: 6 6 6 6执行函数:solve(first,4,5,mid,3,4);
传入的first序列: EG
传入的mid序列: GE
root_m=4
呼叫下列函数:
solve: 5 5 3 3
solve: 6 5 5 4执行函数:solve(first,5,5,mid,3,3);
传入的first序列: G
传入的mid序列: G
root_m=3
呼叫下列函数:
solve: 6 5 3 2
solve: 6 5 4 3执行函数:solve(first,6,5,mid,3,2);
传入的first序列:
传入的mid序列:
该函数执行完毕:solve(first,6,5,mid,3,2);执行函数:solve(first,6,5,mid,4,3);
传入的first序列:
传入的mid序列:
该函数执行完毕:solve(first,6,5,mid,4,3);
G该函数执行完毕:solve(first,5,5,mid,3,3);执行函数:solve(first,6,5,mid,5,4);
传入的first序列:
传入的mid序列:
该函数执行完毕:solve(first,6,5,mid,5,4);
E该函数执行完毕:solve(first,4,5,mid,3,4);执行函数:solve(first,6,6,mid,6,6);
传入的first序列: F
传入的mid序列: F
root_m=6
呼叫下列函数:
solve: 7 6 6 5
solve: 7 6 7 6执行函数:solve(first,7,6,mid,6,5);
传入的first序列:
传入的mid序列:
该函数执行完毕:solve(first,7,6,mid,6,5);执行函数:solve(first,7,6,mid,7,6);
传入的first序列:
传入的mid序列:
该函数执行完毕:solve(first,7,6,mid,7,6);
F该函数执行完毕:solve(first,6,6,mid,6,6);
C该函数执行完毕:solve(first,3,6,mid,3,6);
A该函数执行完毕:solve(first,0,6,mid,0,6);
我们针对test1.c,进行评论。请读者注意以下几点:
1、在solove(…)中,first[]和mid[]中的数据都不会被改动。变动的只是solve(…)所传入的4个参数。其中 spos_f 意指first区间的开始位置(start position of first),epos_f 意指first区间的结束位置(end position of first);同样地 spos_m 是指mid区间的开始位置,epos_m是指mid区间的结束位置。
2、在solove(…)不断递归调用的过程中,其传入的参数在不断地变化。在计算新的传入参数的时候,solove(…)还利用到了root_m。这一计算过程本身并不复杂,但却令我非常费解:这种递归调用和计算的目的究竟何在?排序?查找?实在是不明白。虽然我并不理解整个程序的意义,但是这并不影响对程序运行结果的推算。在比赛的时候,列出一张类似下面这样的一张表,就可以推算出程序的运行结果:
第一行 |
spos_f |
epos_f |
spos_m |
epos_m |
root_m |
第二行 |
0 |
6 |
0 |
6 |
2 |
第三行 |
spos_f+1 |
spos_f+(root_m-spos_m) |
spos_m |
root_m-1 |
|
第四行 |
spos_f+(root_m-spos_m)+1 |
epos_f |
root_m+1 |
epos_m |
3、只有在函数返回之前的最后一句话,第32行,才会有打印的数据输出。而输出的数据,是这个将要返回的函数的first[]的区间中最左侧的字符。因此,我们关心的问题有两个:
①函数在调用的时候,其最左侧的字符下标(spos_f)是多少?
②函数在什么时候返回?
困扰学生的另一个问题就是:1个solove(…)会派生出2个solove(…),这就是所谓的递归,那么他们的执行先后顺序是怎样的呢?
仔细看test1.c的运行结果,就可以知道这个问题的答案。我把她们之间的调用关系图画在下面:(黑箭头表示调用,红箭头表示返回)
有人会觉得,电脑是如何实现这种调用和返回的过程的呢?
电脑用到了一个被称为“栈”的数据结构,这是一个一维线性的数据结构,通过它,能够完成上述图中的调用过程。
栈:http://zh.wikipedia.org/wiki/%E5%A0%86%E6%A0%88
http://en.wikipedia.org/wiki/Stack_%28data_structure%29
具体展示如下:
阅读时,请注意:
1、在栈顶的,都是正在执行的函数
2、上层的函数,是由下层的函数所调用(派生)出来的。上层是下层的儿子。这点很重要,看图的过程中要认真体会。
3、通过这些调用过程,我们用栈,就能组织出上图树状的那种调用/返回结构。
4、入栈,意味着栈顶的函数调用新的函数,产生了新的栈顶;出栈,意味着栈顶的函数执行完毕(消亡),栈顶下移了。
哦,最后的结果是:
DBGEFCA
后记:这种题目令人十分费解。几乎每次算法竞赛,都有这样一个“读程序写运行结果”的题目,令我不明所以。有谁能知道这段代码究竟是什么目的或者用途呢?或者算法竞赛的目的,只是为了比赛一下选手的动手计算速度?