递归和消去递归

  尽管递归程序在执行时间上往往比非递归程序要付出更多,但有很多问题的数学模型或算法设计方法本来就是递归的,用递归过程来描述它们不仅非常自然,而且证明该算法的正确性也比相应的非递归形式容易得多,因此递归不失为是一种强有力的程序设计方法。

  下面来举个例子:已知元素x,判断x是否在a(1:n)中。
算法思想:在a(1:n)中检索x,若存在,返回该元素在a[]中的下标,否则,返回0。
解决这一问题的递归算法可描述如下:


算法4.3 在数组a(1:n)中检索x是否存在
int void Search(int a[],int i,int x) {
//设a[1..n]和x是全局变量
//在a[1..n]中若有元素a[k]=x,则返回x第一次出现的下标k,否则返回0
if(i>n) return 0
else
if(a[i]==x)
return i
else
return Search(i+1);
}//Search
为了确定x是否在a(l:n)中,应首先在主程序中调用Search(1)。
当然该问题也可用迭代形式来描述,但使用递归时就无需使用循环语句了。

在算法设计的初期阶段使用递归,一旦所设计的递归算法被证明为正确且确信是一个好算法时,就可以消去递归,把该算法翻译成与之等价的、只使用迭代的算法。

下面就介绍将直接递归过程翻译成只使用迭代过程的一组规则。对于间接递归过程的处理只需把这组规则作适当修改即可。

1. 在过程的开始部分,插人说明为栈的代码并将其初始化为空。
使用栈来保存递归过程中的断点信息。断点信息包括如下几个内容:
① 调用的参数; ② 局部变量;
③ 调用的中间结果(函数的值) ④ 返回地址
2. 将标号L1附于第一条可执行语句。然后,对于每一处递归调用都用一组执行下列规则的指令来代替。
3. 将所有参数和局部变量的值存入栈。栈顶指针设计成一个全程变量。
4. 建立第i个新标号Li,并将i存入栈。该标号的i值将用来计算返回地址,该标号用在规则7所描述的程序段中。
5. 计算这次调用的各实在参数(可能是表达式)的值,并把这些值赋给相应的形式参数。
6. 插入一条无条件转向语句转向过程的开始部分,实现循环。
7. 若是函数过程,则对递归过程中含有此次函数调用的语句作如下处理:
① 将该语句的此次函数调用部分用从栈顶取回该函数值的代码来代替;
② 其余部分的代码按原描述方式照抄;
③ 将规则4中所建立的标号附于这条语句上。
若此过程不是函数,则将4中建立的标号附于规则6所产生的转移语句后面的那条语句上。
以上步骤是消去过程中的所有递归调用,此外,还需要对递归过程中的return语句进行处理。注意纯过程结束处的end需看成一条没有返回值的return语句。

在每个有return语句的地方,执行下述规则:
8. 如果栈为空,则执行正常返回。
9. 否则,将所有输出参数(即理解为 out 或 inout 型的参数)的当前值赋给栈顶上的那些对应的变量。
10. 如果栈中有返回地址标号的下标,就插入一条此下标从栈中退出的代码,并把这个下标值赋给一个未使用的变量。
11. 从栈中退出所有局部变量和参数的值并把它们赋给对应的变量。
12. 如果这个过程是函数,则插人以下指令,这些指令用来计算紧接在return后面的表达式并将结果值存入栈顶。
13. 用返回地址标号的下标实现对该标号的转向。

  在一般情况下,使用上述规则都可将一个直接递归过程正确地翻译成与之等价的只使用迭代的过程。它的效率通常比原递归模型要高,进一步简化这程序可使效率再次改进。

算法4.4 递归求取最大值
int void Max1(int i){
//这是一个函数过程,它返回A(1:n)中最大元素的最大下标
if(i<n)
{ j=Max1(i+1);
if (a[i]>a[j]) k=i;
else k=j; }
else
k=n;
return k ;
}//Max1
用几个简单的数据实验一下这个算法,就会明白该算法是个递归模型。在运行时间上,由于过程调用和隐式栈管理方面的消费,使我们自然考虑到要消去归。

算法4.5是与算法4.4等价的迭代算法
int void Max2(int i){
int j,k;
int n,a[1..n]; //n和数组a[1..n]是全局变量
iniStack S; //规则1
top=0; //规则1
L1:if(i<n) { //规则2
S[++top]=i ; //规则3
S[++top]=2; //规则4
i++ //规则5
goto Ll; //规则6
L2: j=S[top--]; //规则7
if(a[i]>a[j]) k=i;
else k=j;
}//i<n条件成立时的操作
else
k=n; //if (i<n)
if(top==0) return k; //规则8
else {addr=S[top--]; //规则10
i=S[top--]; //规则11
S[++top]=k; //规则12
if(addr==2) goto L2; //规则13
}//else
}//Max2