一、递归的概念
若在一个函数、过程或者数据结构定义的内部,直接(或间接)出现定义本身的应用,则称它们是递归的,或者是递归定义的。
递归是一种强有力的数学工具,它可使问题的描述和求解变得简洁和清晰。
- 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;
二、递归算法用于解决的问题
三、递归算法的执行过程
递归方法说明如下:
①调用原问题的处理子程序(过程或函数)时,调用程序应给出具体的子程序形参值(与形参结合的实参);
②在处理子问题中,如果又调用原问题的处理子程序,但形参值应是不断改变的量(表达式);
③每递归调用一次自身,系统就打开一“层”与自身相同的程序系列;
④由于调用参数不断改变,将使条件满足,此时就是最后一“层”,不需再调用自身,而是在本层往下执行后继语句,遇到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.
采用过程编写程序如下:
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.
⑵有终止条件,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.
五、递归算法的优缺点
小结
简单地说,递归算法的本质就是自己调用自己,用调用自己的方法去处理问题,可使解决问题变得简洁明了。
递归程序在执行过程中,一般具有如下模式:
①将调用程序的返回地址、相应的调用前的变量都保存在系统堆栈中;
②执行被调用的过程或函数;
③若满足退出递归的条件,则退出递归,并从栈顶上弹回返回地址、取回保存起来的变量值,继续沿着返回地址,向下执行程序;
④否则继续递归调用,只是递归调用的参数发生变化:增加一个量或减少一个量,重复执行直到递归调用结束。