递归

一、递归的概念

若在一个函数、过程或者数据结构定义的内部,直接(或间接)出现定义本身的应用,则称它们是递归的,或者是递归定义的。

递归是一种强有力的数学工具,它可使问题的描述和求解变得简洁和清晰。

  • n!=n*(n-1)!
  • F(n)=F(n-1)+F(n-2)
对于一个递归定义而已,除了要定义递归的方式(即如何递归)外,还必须定义递归的终止条件(即如何停止递归),否则递归将永无止境的进行下去。

Pascal语言中的向前引用

procedure b; forward;
procedure a;
begin
  b;
end;
procedure b;
begin
  a;
end;

二、递归算法用于解决的问题

一般来说,能够用递归解决的问题应该满足以下三个条件:
 
1.需要解决的问题可以化为一个或多个子问题来求解,而这些子问题的求解方法与原来的问题完全相同,只是在数量规模上不同;
2.递归调用的次数必须是有限的;
3.必须有结束递归的条件(边界条件)来终止递归。
 

三、递归算法的执行过程

 
可用递归解决的具体问题:
1.数据的定义形式是按递归定义的。如阶乘。
2.问题的解法按递归算法实现。如回溯算法。
3.数据结构的形式是按递归定义的。如树的遍历。
在递归调用之前,系统需完成三件事:
1.为被调用过程的局部变量分配存储区;
2.将所有的实在参数、返回地址等信息传递给被调用过程保存;
3.将控制转移到被调过程的入口。
从被调用过程返回调用过程之前,系统也应完成三件工作:
1.保存被调过程的计算结果;
2.释放被调过程的数据区;
3.依照被调过程保存的返回地址将控制转移到调用过程。
在计算机中,是通过使用系统栈来完成上述操作的。系统栈的元素会包括值参、局部变量和返回地址。在每次执行递归调用之前,系统自动把本程序所使用到的值参和局部变量的当前值以及调用后的返回地址压栈(保存现场);当每次递归调用结束之后,系统又自动把栈顶元素出栈,覆盖掉相应的值参和局部变量,使他们恢复到递归调用之前的值(恢复现场),然后程序无条件的转向由返回地址所指定的位置继续执行。

 

递归方法说明如下:

①调用原问题的处理子程序(过程或函数)时,调用程序应给出具体的子程序形参值(与形参结合的实参);

②在处理子问题中,如果又调用原问题的处理子程序,但形参值应是不断改变的量(表达式);

③每递归调用一次自身,系统就打开一“层”与自身相同的程序系列;

④由于调用参数不断改变,将使条件满足,此时就是最后一“层”,不需再调用自身,而是在本层往下执行后继语句,遇到end,就返回到上“层”调用此子程序的地方并继续往下执行,如此直到返回主程序。

⑤整个递归过程可视为由往返双向“运动”组成,先是逐层递进,逐层打开新的“篇章”,(有可能无具体计算值)当最终递进达到边界,执行完本“层”的语句,才由最末一“层”逐次返回到上“层”,每次返回均带回新的计算值,直至回到第一次由主程序调用的地方,完成对原问题的处理。

        递归算法表现出处理问题的强大能力。然而,如同循环一样,递归也会带来无终止调用的可能性,因此,在设计递归过程(函数)时,必须考虑递归调用的终止问题,就是递归调用要受限于某一条件,而且要保证这个条件在一定情况下肯定能得到满足。

四、递归应用

例:已知一个一维数组A[1..N](N<50),又已知一整数M。 如能使数组A中任意几个元素之和等于M,则输出YES,反之则为NO。

【分析】对于一个已确定的数组a[1]至a[n]和一个确定的数m,判断能否使数组a中任意几个元素之和等于m,等价于判断能否从数组a中取任意数使其和为m。

       此时若a[n]=m,则可以输出“YES”,否则若n=1,则可以输出“NO”。

       否则可以按以下规则进行判断:对于a中任意元素a[n]只有取与不取两种情况:

       (1)取a[n]:

       则此时问题转化为:对于一个已确定的数组a[1]至a[n-1]和一个确定的数m-a[n],判断能否使数组a中任意几个元素之和等于m-a[n]。

       (2)不取a[n]:

       则此时问题转化为:对于一个已确定的数组a[1]至a[n-1]和一个确定的数m,判断能否使数组a中任意几个元素之和等于m。

       若用函数sum(n,m)表示能否从数组a[1]至a[n]中取任意数使其和为m,只要sum(n-1,m-a[n])和sum(n-1,m)当中有一个值为真,则sum(n,m)为真,否则为假。因此,可以用递归来解此题。

       递归终止条件为: if a[n]=m then sum:=true else if n=1 then sum:=false;

采用函数编写程序如下:

 1 Program ex6_28_1; 
 2 Const   max=50; 
 3 Var   a:array[1..max] of integer; 
 4          n, m, i:integer ; 
 5 Function sum(n,m:integer):boolean; 
 6 Begin 
 7    if a[n]=m then sum:=true 
 8     else 
 9         if n=1 then sum:=false 
10             else sum:=sum(n-1,m-a[n]) or sum(n-1,m); 
11 End; 
12 Begin 
13    readln(n); 
14    for i:=1 to n do 
15     readln(a[i]);
16     readln(m); 
17    if sum(n,m) then writeln('YES') 
18     else writeln('NO'); 
19 End.
View Code

递归调用图例

采用过程编写程序如下:

 1 Program ex6_28_2; 
 2 Const  max=50; 
 3 Var   a:array[1..max] of integer; 
 4         n, m, i:integer ; 
 5         flag : boolean;
 6 Procedure sum(n,m:integer); 
 7 Begin 
 8     if a[n]=m then flag:=true              //利用全局变量flag传递结果
 9       else if n=1 then exit             //n=1作为递归边界,不再递归下去
10       else  begin
11                  sum(n-1,m-a[n]);
12                  sum(n-1,m);
13                end;
14 End; 
15 Begin 
16   readln(n); 
17   for i:=1 to n do 
18      readln(a[i]);
19   readln(m); 
20   flag := false;
21   sum(n,m);
22   if flag then writeln('YES') else writeln('NO'); 
23 End.
View Code
对于递归函数或递归过程,每次递归调用都要分配新的局部变量。除了参数按通常的意义传递外,其它局部量只在本层内有效。在执行下一层调用并返回后,该层局部变量应保持其调用前的值。
 
 
Hanoi(河内/汉诺)塔问题
移n个盘的问题,可以简化为移n-1个盘的问题,移n-1个盘的问题,可以简化为移n-2个盘的问题……一直简化到移1个盘的问题。这符合递归算法的两个条件:⑴每次简化为n-1的用同样方法处理的问题,新问题的规模逐渐减小;
⑵有终止条件,n=0盘全部移完
于是很容易写出其递归过程如下:
 1 过程:
 2 procedure hanoi(n: integer; a, b, c: char); 
 3 // n要移动的盘子数目,a:起始柱,c:目标柱,b:临时柱
 4 begin
 5   if (n = 1) then
 6     writeln(a, ‘->’, c)  // 直接将起始柱顶端的盘子移动到目标柱
 7   else
 8   begin
 9     hanoi(n-1, a, c, b);  // 递归移动前n-1个盘子从起始柱移动到临时柱
10     writeln(a, ‘->’, c);  // 直接将最后一个盘子从起始柱移动到目标柱
11     hanoi(n-1, b, a, c);  // 递归移动前n-1个盘子从临时柱移动到目标柱
12   end;
13 end;
14 
15 主程序:
16 begin
17   read(n);
18   hanoi(n, ‘A’, ‘B’, ‘C’);  // 将n个盘子从A柱移动到C柱
19 end.
Hanoi塔问题递归代码

 

五、递归算法的优缺点

优点
1.能用有限的语句来定义对象的无限集合
2.简化某些复杂问题的处理过程
3.代码的可读性较好
缺点
1.系统开销大,程序的运行效率降低
2.递归的深度受到系统栈空间的限制

小结

简单地说,递归算法的本质就是自己调用自己,用调用自己的方法去处理问题,可使解决问题变得简洁明了。

递归程序在执行过程中,一般具有如下模式:

      ①将调用程序的返回地址、相应的调用前的变量都保存在系统堆栈中;

      ②执行被调用的过程或函数;

      ③若满足退出递归的条件,则退出递归,并从栈顶上弹回返回地址、取回保存起来的变量值,继续沿着返回地址,向下执行程序;

      ④否则继续递归调用,只是递归调用的参数发生变化:增加一个量或减少一个量,重复执行直到递归调用结束。

posted @ 2015-12-06 23:08  ZJQCation  阅读(780)  评论(0编辑  收藏  举报