您的位置:首页 > 其它

递归的运行:从一道“读程序写运行结果”的题目说开来

2011-08-26 17:16 330 查看
在今年暑假的算法竞赛练习题中,有这样一个“读程序写运行结果”的题目(第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

 
[align=left]其中,[/align]

[align=left]第一行,是控制着程序运行的5个关键的变量。[/align]

[align=left]第二行,是这五个变量的当前值。[/align]

[align=left]第三、四行,是solove(…)进行递归调用时,会派生出的2个solove(…)函数的参数。其中第三行所派生出的solve(…)是要先于第四行的solve(…)执行的。[/align]

[align=left]第三、四行所对应的solove(…),在执行的过程中,会计算出各自的root_m,该值是由test1.c的18~24行所测算出来的。[/align]

3、只有在函数返回之前的最后一句话,第32行,才会有打印的数据输出。而输出的数据,是这个将要返回的函数的first[]的区间中最左侧的字符。因此,我们关心的问题有两个:

  ①函数在调用的时候,其最左侧的字符下标(spos_f)是多少?

  ②函数在什么时候返回?

困扰学生的另一个问题就是:1个solove(…)会派生出2个solove(…),这就是所谓的递归,那么他们的执行先后顺序是怎样的呢?

仔细看test1.c的运行结果,就可以知道这个问题的答案。我把她们之间的调用关系图画在下面:(黑箭头表示调用,红箭头表示返回





有人会觉得,电脑是如何实现这种调用和返回的过程的呢?

电脑用到了一个被称为“栈”的数据结构,这是一个一维线性的数据结构,通过它,能够完成上述图中的调用过程。



具体展示如下:

阅读时,请注意:

1、在栈顶的,都是正在执行的函数

2、上层的函数,是由下层的函数所调用(派生)出来的。上层是下层的儿子。这点很重要,看图的过程中要认真体会。

3、通过这些调用过程,我们用栈,就能组织出上图树状的那种调用/返回结构。

4、入栈,意味着栈顶的函数调用新的函数,产生了新的栈顶;出栈,意味着栈顶的函数执行完毕(消亡),栈顶下移了。



         



 



       



 



     



 



       



 



     



 



    



 



     



 



     



 



    



 



    



 



   



 



     



 



   



 



    



 



  



 

哦,最后的结果是:

DBGEFCA

后记:这种题目令人十分费解。几乎每次算法竞赛,都有这样一个“读程序写运行结果”的题目,令我不明所以。有谁能知道这段代码究竟是什么目的或者用途呢?或者算法竞赛的目的,只是为了比赛一下选手的动手计算速度?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐