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

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

这个函数的调用关系如下:

image

注意到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
 
其中,
第一行,是控制着程序运行的5个关键的变量。
第二行,是这五个变量的当前值。
第三、四行,是solove(…)进行递归调用时,会派生出的2个solove(…)函数的参数。其中第三行所派生出的solve(…)是要先于第四行的solve(…)执行的。
第三、四行所对应的solove(…),在执行的过程中,会计算出各自的root_m,该值是由test1.c的18~24行所测算出来的。

3、只有在函数返回之前的最后一句话,第32行,才会有打印的数据输出。而输出的数据,是这个将要返回的函数的first[]的区间中最左侧的字符。因此,我们关心的问题有两个:
  ①函数在调用的时候,其最左侧的字符下标(spos_f)是多少?
  ②函数在什么时候返回?

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

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

递归的运行:从一道“读程序写运行结果”的题目说开来_第1张图片

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

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

栈:http://zh.wikipedia.org/wiki/%E5%A0%86%E6%A0%88
http://en.wikipedia.org/wiki/Stack_%28data_structure%29

具体展示如下:

阅读时,请注意:

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

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

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

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

image          image

 

递归的运行:从一道“读程序写运行结果”的题目说开来_第2张图片        image

 

递归的运行:从一道“读程序写运行结果”的题目说开来_第3张图片      递归的运行:从一道“读程序写运行结果”的题目说开来_第4张图片

 

递归的运行:从一道“读程序写运行结果”的题目说开来_第5张图片        递归的运行:从一道“读程序写运行结果”的题目说开来_第6张图片

 

递归的运行:从一道“读程序写运行结果”的题目说开来_第7张图片      image

 

image     image

 

递归的运行:从一道“读程序写运行结果”的题目说开来_第8张图片      递归的运行:从一道“读程序写运行结果”的题目说开来_第9张图片

 

递归的运行:从一道“读程序写运行结果”的题目说开来_第10张图片      递归的运行:从一道“读程序写运行结果”的题目说开来_第11张图片

 

递归的运行:从一道“读程序写运行结果”的题目说开来_第12张图片     递归的运行:从一道“读程序写运行结果”的题目说开来_第13张图片

 

递归的运行:从一道“读程序写运行结果”的题目说开来_第14张图片     递归的运行:从一道“读程序写运行结果”的题目说开来_第15张图片

 

递归的运行:从一道“读程序写运行结果”的题目说开来_第16张图片    image

 

递归的运行:从一道“读程序写运行结果”的题目说开来_第17张图片      递归的运行:从一道“读程序写运行结果”的题目说开来_第18张图片

 

image    递归的运行:从一道“读程序写运行结果”的题目说开来_第19张图片

 

image     image

 

image   image

 

哦,最后的结果是:

DBGEFCA

后记:这种题目令人十分费解。几乎每次算法竞赛,都有这样一个“读程序写运行结果”的题目,令我不明所以。有谁能知道这段代码究竟是什么目的或者用途呢?或者算法竞赛的目的,只是为了比赛一下选手的动手计算速度?

你可能感兴趣的:(递归)