递归讲解

递归讲解
递归讲解

复习

输入a,b,c,计算m 。已知m=)

,,max(),,max(),,max(c b b a c b b a c b a +?+ 请把求三个数的最大数max(x,y,z)定义成函数和过程两种方法作此题。

递 归

为了描述问题的某一状态,必须用到它的上一状态,而描述上一状态,又必须用到它的上一状态……这种用自已来定义自己的方法,称为递归定义。例如:定义函数f(n)为:

/n*f(n -1) (n>0)

f(n)= |

\ 1(n=0)

则当n>0时,须用f(n-1)来定义f(n),用f(n-1-1)来定义f(n-1)……当n=0时,f(n)=1。

由上例我们可看出,递归定义有两个要素:

(1) 递归边界条件。也就是所描述问题的最简单情况,它本身不再使用递归的定义。

如上例,当n=0时,f(n)=1,不使用f(n-1)来定义。

(2) 递归定义:使问题向边界条件转化的规则。递归定义必须能使问题越来越简单。

如上例:f(n)由f(n-1)定义,越来越靠近f(0),也即边界条件。最简单的情况是f(0)=1。

递归算法的效率往往很低, 费时和费内存空间. 但是递归也有其长处, 它能使一个蕴含递归关系且结构复杂的程序简介精炼, 增加可读性. 特别是在难于找到从边界到解的全过程的情况下, 如果把问题推进一步使其结果仍维持原问题的关系, 则采用递归算法编程比较合适.

递归按其调用方式分为: 1. 直接递归, 递归过程P 直接自己调用自己; 2. 间接递归, 即P 包含另一过程 D, 而D 又调用P.

递归算法适用的一般场合为:

1. 数据的定义形式按递归定义.

如裴波那契数列的定义: f(n)=f(n-1)+f(n-2); f(0)=1; f(1)=2.

对应的递归程序为:

Function fib(n : integer) : integer;

Begin

if n = 0 then fib := 1 { 递归边界 }

else if n = 1 then fib := 2

else fib := fib(n-2) + fib(n-1) { 递归 }

End;

这类递归问题可转化为递推算法, 递归边界作为递推的边界条件.

2. 数据之间的关系(即数据结构)按递归定义. 如树的遍历, 图的搜索等.

3. 问题解法按递归算法实现. 例如回溯法等.

从问题的某一种可能出发, 搜索从这种情况出发所能达到的所有可能, 当这一条路走到" 尽头 "的时候, 再倒回出发点, 从另一个可能出发, 继续搜索. 这种不断" 回溯 "寻找解的方法, 称作" 回溯法 ". 例1、给定N (N>=1),用递归的方法计算1+2+3+4+…+(n-1)+n 。

分析与解答 本题是累加问题可以用递归方法求解。本题中,当前和=前一次和+当前项,而前一次和的计算方法与其相同,只是数据不同,即可利用s(n)=s(n-1)+n 来求解,另外递归调用的次数是有限次,且退出的条件是当n=1时s=1,这恰好符合递归算法的使用条件。

程序代码如下:

program p_1(input,output);

var s,t:integer;

function fac(n:integer):integer;

begin

if n=1 then fac:=1 else fac:=fac(n-1)+n;

end;

begin

read(t); s:=fac(t); writeln(‘s=’,s);

end.

0 fac(6)

1 6+fac(5)=21

2 返回

3

4

5

6 1

例2、阶乘n!=1*2*3…(n-1)*n ,可以改写成n!=(n-1)!*n ,这是阶乘用阶乘定义,但是(n-1)!是n!的简单情况。要求n!必须用同样的方法先求简单情况(n-1)!,要求(n-1)!必须用同样的方法先求简单情况(n-2)!,…最终递归到0!而0!=1。因此n!就是一个递归的描述。阶乘的递归定义:

n!=???=>-010)!

1(*n n n n

program facn(input,output);

var n:integer;y:real;

function fac(n:integer):real;

begin

if n=0 then fac:=1

else fac:=n*fac(n-1)

end;

begin

read(n); y:=fac(n); writeln(n,’!=’,y);

end.

分析程序是如何执行的?

例3 求m 与n 的最大公约数

讨论:从数学上可以知道求m 与n 的最大公约数等价于求n 与(m mod n )的最大公约数。这时可以把n 当作新的m ,(m mod n )当作新的n ,问题又变成了求新的m 与n 的最大公约数。它又等价于求新的n 与(m mod n )的最大公约数……如此继续,直到新的n=0时,其最大公约数就是新的m 。

例如求24与16的最大公约数,等价于求16与(24 mod 16)的最大公约数,即求16与8的最大公约数。它又等价于求8与(16 mod 8)的最大公约数,即求8与0的最大公约数。此时n=0,最大公约数就是8。此过程可简单地列表为

m n

24 16

16

8 8 0

其一般公式是gcd (m,n)=???>=0

)mod ,gcd(0n n m n n m

其中gcd(m,n)代表求m 与n 的最大公约数。按照此公式可以编出如下递归函数的程序。

Program gmn(input,output);

Var m,n,g:integer;

Function gcd(m,n:integer):integer;

Begin

If n=0 then gcd:=m

Else gcd:=gcd(n, m mod n)

End;

Begin

Read(m,n);g:=gcd(m,n);writeln(‘m=’,m,’n=’,n,’gcd=’,g);

End.

例4、写出下面程序运行的结果

procedure p;

begin

write(1);

end;

procedure t(j:integer);

var i:integer;

begin

for i:=1 to 2 do

begin if j=3 then p else t(j+1); end;

end;

begin

t(1); writeln;

end.

例5、相传在古印度的布拉玛婆罗门圣庙的僧侣在进行一种被称为汉诺塔的游戏,其装置是一块铜板,上面有三根杆(编号A 、B 、C ),A 杆上自下而上、由大到小按顺序串上64个金盘。游戏的目标是把 A 杆上的金盘全部移到C 杆上,并仍按原有顺序叠好。条件是每次只能移动一个盘,并且在每次移动都不允许大盘移到小盘之上。现要求

利用递归调用技术给出N 个盘从A 杆移到C 杆的移动过程。

分析:这个移动过程很复杂与烦琐,但规律性却很强。使用递归调用技术来解决这个移动过程,先得找到一个递归调用模型。想要得到汉诺塔问题的简单解法,着眼点应该是移动A 杆最底部的大盘,而不是其顶部的小盘。不考虑64个盘而考虑N 个盘的一般情况。要想将A 杆上的N 个盘移至C 杆,我们可以这

样设想:

1.以C盘为临时杆,从A杆将1至N-1号盘移至B杆。

2.将A杆中剩下的第N号盘移至C杆。

3.以A杆为临时杆,从

B杆将1至N-1号盘移至C 杆。我们看到,步骤2只需移动一次就可以完成;步骤1与3的操作则完全相同,唯一区别仅在于各杆的作用有所不同。这样,原问题被转换为与原问题相同性质的、规模小一些的新问题(下图)。即: HANOI(N,A,B,C) 可转化为 HANOI(N-1,A,C,B)与HANOI(N-1,B,A,C)

其中HANOI中的参数分别表示需移动的盘数、起始盘、临时盘与终止盘,这种转换直至转入的盘数为0为止,因为这时已无盘可移了。这就是需要找的递归调用模型。

程序清单:

program ex5_9;

var a,b,c:char; n:byte;

procedure hanoi(n:byte;a,b,c:char);

begin

if n=1 then writeln(a,’->’,c)

else

begin

hanoi(n-1,a,c,b);

writeln('Move ',a,' to ',c);

hanoi(n-1,b,a,c);

end;

end;

begin

a:='A';b:='B';c:='C';

write('N=');readln(n);

hanoi(n,a,b,c);

end.

var a,b,c,d:integer;

Procedure p(a:integer;var b:integer);

Var c:integer;

Begin

a:=a+1;b:=b+1;c:=c+1;d:=d+1;

If a<3 then p(a,b);

Writeln(a,b,c,d);

End;

Begin

a:=1;b:=1;c:=1;d:=1;p(a,b);writeln(a,b,c,d);

End.

例7、把一个十进制整数转化为K进制数。(K<=10)

分析与解答根据数制转化的规则,把一个十进制数转化为K进制数,只要用K依次去除这个数及其商,所得的余数依次为K进制数相继的低位数字。依照这个方法,直到商为0为止,最后的余数作为K进制数的最高位数字。

本题是否选用递归算法来做呢?

我们看一看它是否符合递归使用的三个条件:

1、前后数据之间存在一定的联系。显然,前一次求得的商就是后一次进一步求余的被除数,这个条件符合。

2、有限次递归调用。对一个十进制数向K进制数转化的问题,由于K是有限的,所以意味着递归调用也是有限次。

3、结束条件是:商为0则递归结束。

由此看来,本题可以使用递归算法实现。

程序代码如下:

program p_3(input,output);

var n,k:integer;

procedure tentok(n,k:integer);

var d:integer;

begin

d:=n mod k; n:=n div k; if n<>0 then tentok(n,k); write(d); end;

begin

write(‘please input n and k(-10):’);

readln(n,k);tentok(n,k);

writeln;

end.

例8、把自然数N分解为若干个自然数之积。

如输入12

输出:

12=12

12=3*4

12=2*6

12=2*2*3

total:4

参考程序:

var path:array[1..1000] of integer;total,n:integer;

procedure find(k,sum,dep:integer);

var b,d:integer;

begin

if sum=n then

begin

write(n,'=',path[1]);

for d:=2 to dep-1 do write('*',path[d]);

writeln;inc(total);

exit;

end;

if sum>n then exit;

for b:=trunc(n/sum)+1 downto k do

begin

path[dep]:=b;find(b,sum*b,dep+1);

end;

end;

begin

readln(n);total:=0;find(2,1,1);writeln('total:',total);

readln;

end.

例9、要求找出具有下列性质的数的个数(包含输入的自然数n):

先输入一个自然数n(n<=500),然后对此自然数按照如下方法进行处理:(1)不作任何处理(即自然数n本身);

(2)在它的左边加上一个自然数,但该自然数不能超过原数的一半;(3)加上数后,继续按此规则进行处理,直到不能再加自然数为止。

样例:

输入:6

满足条件的数为

6

16

26

126

36

136

输出:6

program p_lx3_1(input,output);

var n,I:integer;s:real;

procedure qiu(x:integer);

var k:integer;

begin

if x<>0 then begin

s:=s+1;

for k:=1 to x div 2 do qiu(k);

end;

end;

begin

readln(n);s:=0;qiu(n);writeln(s:2:0);

end.

Program p_lx3_2(input,output);

Var n,I:integer;s:real;a:array[1..100] of integer;

Procedure print(dep:integer);

Var I:integer;

Begin

For I:=dep-1 downto 1 do

Write(a[I]);

Writeln;

End;

Procedure qiu(x:integer;dep:integer);

Var k:integer;

Begin

Print(dep);s:=s+1;

If x<>1 then

For k:=1 to x div 2 do

Begin a[dep]:=k;qiu(k,dep+1); end;

End;

Begin

Readln(n);s:=0;a[1]:=n;qiu(n,2);writeln(s:2:0);

End.

仔细分析这二个程序的区别。

例10 2的幂次方表示

任何一个正整数都可以用2的幂次方表示.例如:137=2^7+2^3+2^0。同时约定次方用括号来表示,即a^b可表示为a(b)。由此可知,137可表示为:2(7)+2(3)+2(0),进一步:7=2^2+2+2^0 (2^1用2表示);3=2+2^0;所以最后137可表示为:2(2(2)+2+2(0))+2(2+2(0))+2(0)。又如:1315=2^10+2^8+2^5+2+1;

所以1315最后可表示:2(2(2+2(0))+2)+2(2(2+2(0)))+2(2(2)+2(0))+2+2(0)

输入:正整数(n≤20000)

输出:符合约定的n的0,2表示(在表示中不能有空格)

题解

解法1:递归法

2的幂次方由高幂向低幂分解

1.计算最接近n(小于n)的2的次幂e

2e≤n<2e+1

设x=2e。

2.从e开始按照次幂递减的方向分解(0≤i≤e)

设x1—项数。若n≥x,则分解出x=2i的2幂次方表示:

⑴若x1>0(非第一项),则输出’+’;

⑵分析次幂i

i=0,输出2(0); {递归边界}

i=1,输出2;

i>1,输出2(i的2幂次方表示) {递归}

⑶准备分解下一项

x1←x1+1;n←n-x;

x←x div 2;(无论n是否大于等于n)

由此得出算法:

procedure solve(n:word);

var

e,i,x1:byte;

x:word;

begin

x←16384;e←14; {214<20000<215}

while x>n do {计算最接近n(小于n)的x=2e}

begin

x←x div 2; e←e-1

end;{while}

x1←0;

for i←e downto 0 do {逐位分解2的幂次i}

begin

if n≥x {若能分解x=2i}

then begin

if x1>0 then write(’+’); {若当前是中间项} case i of {根据次幂i分解x的2幂次方表示} 0: write(’2(0)’);

1: write(2);

else begin

write(’2(’);

solve(i);

write(’)’)

end{else}

end;{case}

x1←x1+1; n←n-x {准备分解下一项x=2i-1}

end;{then}

x←x div 2

end{for}

end;{solve}

解法2:program p_lx6(input,output);

var n:integer;

procedure bin(k:integer);

var b:array[0..15] of integer;I,p:integer;first:Boolean;

begin

p:=-1;

if k=0 then write(0)

else begin

while k>0 do

begin

inc(p);b[p]:=k mod 2;k:=k div 2;

end;

first:=true;

for I:=p downto 0 do

if b[I]=1 then

begin

if first then first:=false else write(‘+’);

if I=1 then write(‘2’)

else begin

write(‘(’);

bin(i);

write(‘)’);

end;

end;

end;

end;

begin

write(‘n=’);readln(n);bin(n);writeln;

end.

使用递归求解问题,通常可以将一个比较大的问题层层转化为一个与原问题相类似、规模较小的问题,进而最终导致原问题的解决。

例11 求数字的乘积根。一个正整数的数字的乘积N的定义是:这个整数中非零数字的乘积。例如,整数999的数字乘积为9*9*9,即729。729的数字乘积为7*2*9,即126。126的数字乘积为1*2*6,即12。12的数字乘积为1*2,即2。一个正整数的数字乘积根N是这样得到的:反复取该整数的数字乘积,直到得到一位数字为止。例如,在上面的例子中数字的乘积根是2。

编写一个程序,输入一个正整数(长度不超过200位数字),输出计算其数字乘积根的每一步结果。

分析:每一步得到的乘积根比它的上一步的位数要小,显然存在递归过程,递归结束的条件是乘积根的位数等于1。具体程序编码如下:

program ex5_17;

var st:string;

procedure init;

begin

writeln(‘please input:’);readln(st);

end;

procedure make(ss:string);

var a,b:array[1..200] of integer;I,j,x,code:integer;w:string;

begin

fillchar(a,sizeof(a),0);

for I:=1 to length(ss) do

val(ss[I],a[I],code);

fillchar(b,sizeof(b),0);

x:=1;b[1]:=1;

for I:=1 to length(ss) do

begin

for j:=1 to x do

if a[I]<>0 then b[j]:=b[j]*a[I];

for j:=1 to x do

begin

b[j+1]:=b[j+1]+b[j] div 10;

b[j]:=b[j] mod 10;

end;

if b[x+1]>0 then x:=x+1;

end;

ss:=‘’;

for I:=x downto 1 do

begin

str(b[I],w);ss:=ss+w;

end;

writeln(ss);

if length(ss)>1 then make(ss);

end;

begin

init;writeln(st);make(st);readln;

end.

例12、骨牌铺法

有1*n的一个长方形,用一个1*1、1*2、1*3的骨牌铺满方格。例如当n=3时为1*3的方格。此时用1*1,1*2,1*3的骨牌铺满方格,共有四种铺法。图4.4.3列出了四种铺法。

输入n(0<=n<=30) 输出铺法总数

题解:这道题可以采用猜测法,从具体的n=1,2,3,......开始,列举出结果,根据列举的部分结果进行猜测,推导出公式。这个猜测推导过程留给读者完成。问题是:这种方法中“猜”和“凑”的成分比较比较多,容易出错。我们不妨采用组合数学常用的待定系数进行归纳和推导。设推导公式如下:

f(n)=a*f(n-1)+b*f(n-2)+c*f(n-3)+d*f(n-4)+....(a,b,c,d...是常系数)

即1*n的长方形的铺法由全部(a种)1*(n-1)的长方形铺法总数加上全部(b种)1*(n-2)的长方形的铺法总数加上全部(c种)1*(n-3)的长方形的铺法总数......注意排除重复情况。

(1)将n格分成1格和n-1格,计算f(n-1)的系数a。右端1格的铺法有一种(图4.4.4(a))。显然,在一格中只有一种铺法,即f(n-1)的系数a=1。

(2)将n格分成2格和n-2格,计算f(n-2)的系数b。右端2格的铺法有两种(图4.4.4(b))。由图可见,(b)的铺法包含在(a)的铺法中,而(c)的铺法不同于(a),因此f(n-2)的系数b=1。

n-1格(a)

n-2格(b)

n-2格(c) 图4.4.4

(3)将n格分成3格和n-3格,计算f(n-3)的系数c。右端3格的铺法有两种(图4.4.5)。由图可见,(d)

(e)(f)的铺法都可以归结到1格或2格中去,只有1*3的铺法(g)属于新的,因此f(n-3)的系数c=1。将n格分成n-x格和x格(4<=x<=n-4)的情况都是重复的,因此不再讨论。由此得出:

n-3格(d)

n-3格(g)

f(n)=f(n-1)+f(n-2)+f(n-3) (n>=5)

var n:integer;

function f(i:integer):longint;

begin

if i in[1..2]

then f:=i

else if i=3

then f:=4

else f:=f(I-1)+f(I-2)+f(I-3);

end;

begin

readln(n); writeln(f(n));

end.

“铺砖问题”有推广价值。例如某人走n级的楼梯,每步可以走1级、2级或3级,走完n级楼梯共有多少种走法。这个问题的数学意义和解法与“铺砖问题”相同。

例13、楼梯共有N阶台阶,上楼可以一步上一个台阶,也可以一步上二个台阶。编一个程序,计算上N阶

台阶,共有多少种走法?

算法分析:根据前面的例子,我们知道递归程序执行包含递归和递推二个过程,而这二个过程又都是根据一个递推公式进行的,所以在这里我们先试着找出一个能体现不同规模问题之间关系的递推公式。

找出递推公式:我们可以试着用逆向思维来找这个递推公式,即从最终目标考虑,而不是从初始状态考(回忆一下, 在计算N!=N*(N-1)!不也是这样来的吗?)。最终目标是指到达第N 阶台阶,然后再逆向分析,到达N 阶台阶的前一步在那里?只是是在N-1阶或N-2阶上。现在我们已经得到了具有直接联系的二种状态,接下来就去找他们之间的关系式。为了表示的简洁,设上N-1阶台阶共有F (N-1)种走法,上N-2阶台阶共有F (N-2)种走法,则上N 阶台阶有F (N )种走法。假设你已到达N-1阶,现在你有多少种走法到达N 阶呢?当然只有一种(跨一步到达N 阶)。因此通过N-1阶到达N 阶的走法有F (N-1)种。再假设你已到达N-2阶,从这里到N 阶有多少种走法?有二种,一种是先到第N-1阶,再到N 阶(一步1阶走);另一种是直接跨2阶到N 阶。第一种走法实际已包含在先到N-1阶再到N 阶的走法中,所以在计算总走法时应不再考虑,否则就会重复计算。第二种走法中从N-2阶到N 阶(中途不经过N-1阶)只有一种走法(一步跨二阶直接到达),所以先到N-2阶(不经过N-1阶)到达N 阶的走法有F (N-2)种。综上所述,到达N 阶的走法有:F (N )=F (N-1)+F (N-2)。

参考程序

var s,n:integer;

function f(n:integer):integer;

begin

if n<3 then f:=n else f:=f(n-1)+f(n-2);

end;

begin

readln(n);s:=f(n);writeln(‘s=’,s);

end.

问题求解(直接给出答案就可)。

在平面上有n 条直线,且无三线共点。问这些直线有多少种不同的交点数?

在2*n 的一个长方形方格中,用一个1*2的骨牌铺满方格,例如当n=3时,为2*3方格,骨牌的铺放方案有三种(图1)。输入n =5;输出铺放总数。

例14、简单的背包问题。设有一个背包,可以放入的重量为s 。现有n 件物品,重量分别为w 1,w 2,…,w i ,…w n , w i (1<=I<=n),均为正整数。从n 件物品中挑选若干件,使得放入背包的重量之和正好为s 。

算法分析:

用knap(s,n)代表这一问题。(1)先取最后一个物品w n 放入包里,若w n =s ,正好放入包中,问题解决,输出结果(n, w n )。(2)若w n 1),那么问题转化为从剩余的n-1个物品中选取若干个,使得它们的重量和等于包里剩下的可放入重量(s-w n ),即knap(s,n)->knap(s-w n ,n-1);而选中的w n 是否有效还要看后续的问题knap(s-w n ,n-1)是否有解,无解的话说明先取的w n 不合适,就要放弃w n ,在剩余的物品中重新开始挑选,即由knap(s,n)->knap(s,n-1)。(3)若w n >s ,则不能放入包中,还得继续挑选;若还剩物品(即n>1),那么问题转化为从剩余的n-1个物品中选取若干个,使得它们的重量和等于s ,即knap(s,n)->knap(s,n-1)。

显然(2)、(3)中出现了递归定义,而(1)是递归结束的条件;但这是有解时出现的递归结束条件,然而还有可能无解,(2)、(3)中所剩物品不够的话(n<=1)问题就不能继续,这也是递归结束的条件。为了标志knap(s,n)是否有解,将knap(s,n)设置为布尔函数,true 为有解,false 无解。其伪代码为: procedure knap(s,n);

case s-w n of

=0:[knap:=true;print(n, w n )]

>0;[if n>1 then if knap(s-w n ,n-1)=true then [knap:=true;print(n,w n )]

else knap:=knap(s,n-1)

else knap:=false]

<0;[if n>1 then knap:=knap(s,n-1) else knap:=false]

end;

用pascal 语言具体实现时,将w 1,w 2,…w n 存入数组w:array[1..n] of integer 。为了使用case 语句,设计一个符号函数sng(x)来区分s-w n 的三种情况:

sng(x)=?????<->=0

101

00x x x 为了程序的易读性,源程序没有使用过于精简的方法。

Program exp1_4{简单背包问题}

Const m=10;

Var w:array[1..m] of integer;x,y,I:integer;f:Boolean;

Function sng(x:integer):integer;

Begin

Sng:=0;

If x>0 then sng:=1;

If x<0 then sng:=-1;

End;

Function knap(s,n:integer):Boolean;

Begin

Case sng(s-w[n]) of

0:begin knap:=true;writeln(‘number:’,n:4,’weight:’,w[n]:4); end;

1:begin if n>1 then if knap(s-w[n],n-1)=true {递归①} then begin

knap:=true;writeln(‘number:’,n:4,’weight:’,w[n]:4);

end else knap:=knap(s,n-1) {递归②}

else knap:=false

end; -1:if n>1 then knap:=knap(s,n-1) {递归③}else knap:=false;

end;

end;

begin

write(‘The number of object:’);

repeat readln(y) until y<=m;write(‘Total weight=’);readln(x);

write(‘Weight of each object:’);

for I:=1 to y do read(w[I]);

f:=knap(x,y);

if not(f) then writeln(‘Not answer ’);

end.

下面是两组运行结果:

The number of object:3

Total weight=7

Weight of each object:9 6 2

Not answer

The number of object:5

Total weight=10

Weight of each object:1 6 2 7 5

number:1 weight:1

number:3 weight:2

number:4 weight:7

源程序中三处有递归调用式,分别标以①②③,我们以上面这组数据来分析一下递归过程:knap(10,5)(1 6 2 7 5){先取5放入包内,问题转化为下一步}

}knap(5,4) (1 6 2 7)

递归③}knap(5,3) (1 6 2)

递归①}knap(3,2) (1 6)

递归③}knap(3,1) (1)

knap:=false

逐级返回的函数值是false;由于knap(5,4)=false;故首选的5不能用;于是knap(10,5)转向另一路,如下图所示。

主程序knap(10,5) {放弃(1 6 2 7 5)中的5,问题转化为下一步}

递归②}knap(10,4) 选(1 6 2 7)的7

递归①}knap(3,3) 选(1 6 2)

递归①}knap(1,2) 选(1 6),但6大于剩下的容量1

递归③}knap(1,1) 选(1)

writeln(1);knap:=true

对照源程序读懂了上面的递归调用过程,可以体会到递归算法中可隐含回溯,如首选5,并一路选下去,发现不行能回到出发点,重选7,再一路选下去,直到成功;还可以看到先选中的后打印;这些都说明系统为每一层的返回点、局部量都开辟了保存区,即所谓栈。

如果满足条件的答案不止一个,该程序只给出了满足条件的第一组答案,如

The number of object:5

Total weight=10

Weight of each object:1 4 2 7 5

number:1 weight:1

number:3 weight:4

number:4 weight:5

例15、输入一个非负整数,输出这个数的倒序数。如输入123,输出321。

算法分析:

procedure 数字倒序

begin

输出n的最右边的一个数字;

if 还有数字 then 将余下的“数字倒序”;

end

过程如下:

procedure reverse(n:integer);

var nr,nl:integer;

begin

nr:=n mod 10;{取n最右边的一个数字}

write(nr);

nl:=n div 10;{取n余下的数字}

if nl<>0 then reverse(nl);

end;

分析reverse(123)执行过程:每一次调用都为过程的局部变量分配存储单元,而每一次调用结束都返回上一次调用句的下一句(这和递归函数每次返回点不同)

主程序

nl=0是递归结束的条件

例16、折半查找的递归算法。

折半查找是在一列升序(或降序)的数中查找目的数。将这一列数置于a数组中,目的是寻找x,用top 指向低端,bot指向高端(top<=bot),mid指向中间,然后进行以下三种比较:(假设a数组升序)(1)若x=a[mid],则表示找到。

(2)若x

(3)若x>a[mid],则进行下一步查找,top变成mid+1,bot不变。

所谓进一步查找,就是重复前面的步骤,但查找的范围改变了。过程结束的条件有两种:要么找到了,即x=a[mid];要么找不到,即出现top>mid。

源程序:

program exp1_6;

const n=20;

type arr=array[1..n] of integer;

var a:arr;first,last,I,x:integer;

procedure search(x,top,bot:integer);

var mid:integer;

begin

if top<=bot then

begin

mid:=(top+bot) div 2;

if x=a[mid] then write(‘find:’,x,’position’,mid)

else if x

else search(x,mid+1,bot) {递归调用}

end

else writeln(‘not been found’,x)

end;

begin

writeln(‘input 20 ordering number:’);

for I:=1 to 20 do read(i);readln; {按升序输入20个数据}

write(‘input finding num:’);readln(x);

first:=1;last:=n;

search(x,first,last);

end.

例17、输出n个元素的无重复的全排列。N个元素有n!种不同排列。

1个元素直接输出;

2个元素有两种排列,如(a b)(b a);

3个元素以(a b c)为例,有:

a b c

a c b

b a c

b c a

c b a

c a b

分析这些排列,一个简单的算法是:

(1)a后随(b c)的所有排列

(2)b后随(a c)的所有排列

(3)c后随(b a)的所有排列

上面(2)是将(1)中的a、b互换位置;上面(3)是将(1)中的a、c互换位置。这里意味着可以用循环的方法来重复执行“交换位置,后随剩余序列的所有的排列”;而对剩余序列可以再使用这个方法,这就成了递归调用,后随的元素没有时,就到了递归的边界。

对于n个元素a=(a1a2…ak…an),设过程prem(a,k,n)是求a的第k到n个元素的全排列,设swap(a,k,I)是将a的第k个元素和第I个元素对换,I=k,…,n。

program exp1_7;

var a:string;k,n:integer;

procedure swap(var a:string;k,I:integer);

var t:char;

begin

t:=a[k];a[k]:=a[I];a[I]:=t;

end;

procedure perm(a:string;k,n:integer);

var I:integer;

begin

if k=n then writeln(a)

else for I:=k to n do

begin

swap(a,k,I);perm(a,k+1,n);

end

end;

begin

readln(a);n:=length(a);perm(a,1,n);

end.

例18棋子移动。有2n个棋子(n>=4)排成一行,开始位置为白子全部在左边,黑子全部在右边,如图:○○○○○●●●●●

移动棋子的规则是:每次必须同时移动相邻两个棋子,颜色不限,可以左移也可以右移到空位上去,但不能调换两个棋子的左右位置。每次移动必须跳过若干个棋子(不能平移),要求最后能移成黑白相间的一行棋子。如n=5时,成为:

○●○●○●○●○●

算法分析:

从n=4开始尝试,意图找到某种“规律性”。

N=4 ○○○○●●●●

第一步○○○-—●●●○●

第二步○○○●○●●-—●

第三步○-—●○●●○○●

第四步○●○●○●-—○●

第五步-—○●○●○●○●

n=5 ○○○○○●●●●●

第一步○○○○-—●●●●○●

第二步○○○○●●●●-—○●

已归结为n=4的情况,即第三至第七步同n=4的第一至第五步。

n=6 ○○○○○○●●●●●●

第一步○○○○○-—●●●●●○●

第二步○○○○○●●●●●-—○●

已归结为n=5的情况,即第三至第九步同n=5的第一至第七步。

由此可见,n=m的移动可以先移动两步,然后归纳为n=m-1的移动。

分配一个数组c[1..max]用来作为棋子移动的场地,开始时c[1]~c[m]中为白子(用字母O来表示),c[m+1]~c[2n]中为黑子(用*表示),c[2n+1],c[2n+2]为移动时的工作场地(初值为--),这些初始化工作用过程init完成;最后结果在c[3]~c[2n+2]中。对n=4时的描述:

第一步移动c[4]、c[5]和c[9]、c[10]

第二步移动c[8]、c[9]和c[4]、c[5]

第三步移动c[2]、c[3]和c[8]、c[9]

第四步移动c[7]、c[8]和c[2]、c[3]

第五步移动c[1]、c[2]和c[7]、c[8]

用过程mvtosp(k:integer)移动c[k]和c[k+1]到工作场地,移动后c[k]和c[k+1]成为工作场地;其内含print打印每次移动后的结果。

主要过程mv(n:integer)移动n对(2n个)棋子,由初始状态到终局(黑白相间)。

Program exp1_10;

Const max=100;

Var n,st,sp:integer;c:array[1..max] of char;

Procedure print;

Var I:integer;

Begin

Write(‘step’,st:2,’:’);

For I:=1 to 2*n+2 do write(c[I]);

Writeln;

St:=st+1;

End;

Procedure mvtosp(k:integer);

Var j:integer;

Begin

For j:=0 to 1 do

begin C[sp+j]:=c[k+j];c[k+j]:=‘-’;end;

sp:=k;

print;

end;

procedure mv(n:integer);

var I,k:integer;

begin

if n=4 then begin mvtosp(4);mvtosp(8);mvtosp(2);mvtosp(7);mvtosp(1);end

else begin mvtosp(n);mvtosp(2*n-1);mv(n-1) end

end;

procedure init(n:integer);

var I:integer;

begin

st:=0;sp:=2*n+1;for I:=1 to n do c[I]:=’O ’;

for I:=n+1 to 2*n do c[I]:=’*’;

c[2*n+1]:=‘-’;c[2*n+2]:=‘-’;

print;

end;

begin

repeat write(‘input n(n>3):’);readln(n) until n>3;

init(n);mv(n);readln;

end.

习题一已知 f(x,n)=x n n n +++-+-+1...)2()1(

计算x=3.1,n=15及x=8.1,n=10时的f 值,将f 定义成递归函数作。

习题二已知 f(x,n)=x x

n x

n x n x

+++

-+-+

1......

)2()1(

计算x=3.57,n=20时的f 值。将f 定义成递归函数和递归过程两种方法作。

习题三 求菲波拉契数列的第10项和第20项的值。已知:

a 0=0

a 1=1

a 2=a 0+a 1

a 3=a 1+a 2

将第n 项的a(n)写成递归函数计算。

习题四 计算x n 。X,n 由键盘输入。将x n 写成递归函数计算。N 允许为正,负整数和零。

习题五 计算勒让德多项式的值。已知

P n(x)=

用递归函数计算。X,n由键盘输入。

习题六顺序读入字符,以‘?’结束,然后以和输入相反次序输出读入的字符,用递归过程作。

例19输入N个字符,然后以倒序输出(用递归实现)。

Program digui03(input,output);

Const n=8;

Type mat=array[1..n] of char;

Var I:integer;a:mat;

Procedure print(I:integer);

Begin

If I=n then write(a[I])

Else begin print(I+1);write(a[I]); end;

End;

Begin

For I:=1 to n do readln(a[I]);

I:=1;print(i);readln;

End.

例20有n只猴子选大王,先从头到尾1至3报数,报到3的猴子退出,报至尾后,再从尾到头1至3报数,报到3的猴子退出…依次类推,当剩下两只猴子时,报1的为大王。问若想当大王,应站在什么位置。解析:十只猴子1-10编号,则出圈的次序为

猴子编号:12345678910

出圈次序:369725410剩下8和1时,8号猴子报1为大王

程序如下:

program p4(input,output);

type arr=array[1..100] of integer;

var h:arr;I,j,m,n:integer;

procedure num(var a:arr;var n,I:integer;m:integer);

var f,k,s:integer;

begin

if n>=3 then

begin

s:=0;k:=I mod 2;{k 为1表示从头到尾,k为0表示从尾到头}

case k of

1:for f:=1 to m do {正向报数}

if a[f]<>0 then

begin

s:=s+1;

if s=3 then begin s:=0;n:=n-1;a[f]:=0; end;

end;

0:for f:=m downto 1 do{反向报数}

if a[f]<>0 then

begin

s:=s+1; if s=3 then begin s:=0;n:=n-1;a[f]:=0;end;

end;

end;

I:=I+1;num(a,n,I,m);{递归调用}

End;

End;

Begin {主程序}

Write(‘input n:’);readln(n);

For I:=1 to n do h[I]:=1; {报数前赋初值1}

J:=1;m:=n;num(h,n,j,m); {调用过程num}

If j mod 2=1 then

Begin

I:=1;while h[I]=0 do I:=I+1;

Writeln(‘the leader is:,I);

End

Else begin

I:=m;while h[I]=0 do I:=I-1;

Writeln(‘the leader is:’,I);

End;

End.

习题七程序:

program lx2_7_1(input,output);

var s,n:integer;

function f(n:integer):integer;

begin

if n=1 then f:=1 else f:=n*n+f(n-1)

end;

begin

write(‘input n:’);readln(n);s:=f(n);writeln(‘f(’,n,‘)=’,s)

end.

该程序的功能是: 。当n 的值为6时,程序的运行结果是:

习题八已知函数ack 的计算公式如下:

ack(m,n)=?????≠≠--=+=-00))1,(,1(01

0)1,1(n m n m ack m ack m n n m ack 且

计算ack(1,2)和ack(2,2)的值。

习题九用递归的方法求Hermite 多项式的值

h n (x)=?????>--==--1

)()1(2)(2120121n x h n x xh n x

n n n 对给定的x 和正整数n ,求多项式的值。

习题十用递归的方法求12+22+32+…+n 2。

习题11用辗转相除法求两个正整数的最大公约数,用递归和非递归的形式编程。

习题12黑白棋子各n 个,按下图方式排列

●●……●○○○……○

算法设计题详解

算法设计的特征:有穷性,确定性,输入和输出,可行性 运行算法的时间:硬件的速度。书写程序的语言。问题的规模,编译生成程序的代码质量算法复杂度: 时间复杂度和空间复杂度 1.迭代法 迭代法又称为辗转法,是用计算机解决问题的一种基本方法,为一种不断用变量的旧值递推新值的过程,与直接法相对应,一次性解决问题。迭代法分为精确迭代和近似迭代,“二分法”和“牛顿迭代法”属于近似迭代法。迭代法利用计算机运算速度快、适合做重复性操作的特点,让计算机对一组指令(或一定步骤)进行重复执行,在每次执行这组指令(或这些步骤)时,都从变量的原值推出它的一个新值。 利用迭代算法解决问题,需要做好以下三个方面的工作: 1.确定迭代变量(在可以用迭代算法解决的问题中,至少存在一个直接或间接地 不断由旧值递推出新值的变量,这个变量就是迭代变量。) 2. 建立迭代关系式(所谓迭代关系式,指如何从变量的前一个值推出其下一个值 的公式(或关系)。迭代关系式的建立是解决迭代问题的关键,通常可以顺推 或倒推的方法来完成。) 3.对迭代过程进行控制(在什么时候结束迭代过程?这是编写迭代程序必须考虑 的问题。不能让迭代过程无休止地重复执行下去。迭代过程的控制通常可分为 两种情况:一种是所需的迭代次数是个确定的值,可以计算出来;另一种是所 需的迭代次数无法确定。对于前一种情况,可以构建一个固定次数的循环来实 现对迭代过程的控制;对于后一种情况,需要进一步分析出用来结束迭代过程 的条件。) 2.穷举搜索法 穷举搜索法是对可能是解的众多候选解按某种顺序进行逐一枚举和检验,并从众找出那些符合要求的候选解作为问题的解。 即本方法使用可以理解为暴力循环方法,穷举所有可能性,一般这种方法的时间效率太低,不易使用。但是方法简单,易理解。 3.递推法 递推是计算机数值计算中的一个重要算法,思路是通过数学推导,将复杂的运算化解为若干重复的简单运算,以充分发挥计算机长于重复处理的特点。递推法: 递推法实际上是一种递推关系,就是为了得到问题的解,把它推到比原问题简单的 问题求解,可分为顺推法和倒推法。 i.顺推法,就是先找到递推关系式,然后从初始条件出发,一步步地按 递推关系式递推,直至求出最终结果。 ii.倒推法,就是在不知道初始条件的情况下,经某种递推关系而获知问题的解,再倒过来,推知它的初始条件。 4.递归法(递推加回归) 一个过程或函数在其定义或说明中又间接或间接调用本身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题类似的规模较小的问题来求解,递 归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了 程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。用递归思想 写出的程序往往十分简洁易懂。 一般来说,递归需要有边界条件、递归前进段和递归前往段。当边界条件不满脚时,递归前进;当边界条件满脚时,递归前往。

递归算法详解

递 归 冯文科 一、递归的基本概念。 一个函数、概念或数学结构,如果在其定义或说明内部直接或间接地出现对其本身的引 用,或者是为了描述问题的某一状态,必须要用至它的上一状态,而描述上一状态,又必须用到它的上一状态……这种用自己来定义自己的方法,称之为递归或递归定义。在程序设计中,函数直接或间接调用自己,就被称为递归调用。 二、递归的最简单应用:通过各项关系及初值求数列的某一项。 在数学中,有这样一种数列,很难求出它的通项公式,但数列中各项间关系却很简单,于是人们想出另一种办法来描述这种数列:通过初值及n a 与前面临近几项之间的关系。 要使用这样的描述方式,至少要提供两个信息:一是最前面几项的数值,一是数列间各项的关系。 比如阶乘数列 1、2、6、24、120、720…… 如果用上面的方式来描述它,应该是: ???>==-1 ,1,11n na n a n n 如果需要写一个函数来求n a 的值,那么可以很容易地写成这样:

这就是递归函数的最简单形式,从中可以明显看出递归函数都有的一个特点:先处理一 些特殊情况——这也是递归函数的第一个出口,再处理递归关系——这形成递归函数的第二个出口。 递归函数的执行过程总是先通过递归关系不断地缩小问题的规模,直到简单到可以作为 特殊情况处理而得出直接的结果,再通过递归关系逐层返回到原来的数据规模,最终得出问题的解。 以上面求阶乘数列的函数)(n f 为例。如在求)3(f 时,由于3不是特殊值,因此需要计 算)2(*3f ,但)2(f 是对它自己的调用,于是再计算)2(f ,2也不是特殊值,需要计算 )1(*2f ,需要知道)1(f 的值,再计算)1(f ,1是特殊值,于是直接得出1)1(=f ,返回上 一步,得2)1(*2)2(==f f ,再返回上一步,得62*3)2(*3)3(===f f ,从而得最终解。 用图解来说明,就是 下面再看一个稍复杂点的例子。 【例1】数列}{n a 的前几项为

算法设计及分析递归算法典型例题

算法递归典型例题 实验一:递归策略运用练习 三、实验项目 1.运用递归策略设计算法实现下述题目的求解过程。 题目列表如下: (1)运动会开了N天,一共发出金牌M枚。第一天发金牌1枚加剩下的七分之一枚,第二天发金牌2枚加剩下的七分之一枚,第3天发金牌3枚加剩下的七分之一枚,以后每天都照此办理。到了第N天刚好还有金牌N枚,到此金牌全部发完。编程求N和M。 (2)国王分财产。某国王临终前给儿子们分财产。他把财产分为若干份,然后给第一个儿子一份,再加上剩余财产的1/10;给第二个儿子两份,再加上剩余财产的1/10;……;给第i 个儿子i份,再加上剩余财产的1/10。每个儿子都窃窃自喜。以为得到了父王的偏爱,孰不知国王是“一碗水端平”的。请用程序回答,老国王共有几个儿子?财产共分成了多少份? 源程序: (3)出售金鱼问题:第一次卖出全部金鱼的一半加二分之一条金鱼;第二次卖出乘余金鱼的三分之一加三分之一条金鱼;第三次卖出剩余金鱼的四分之一加四分之一条金鱼;第四次卖出剩余金鱼的五分之一加五分之一条金鱼;现在还剩下11条金鱼,在出售金鱼时不能把金鱼切开或者有任何破损的。问这鱼缸里原有多少条金鱼? (4)某路公共汽车,总共有八站,从一号站发轩时车上已有n位乘客,到了第二站先下一半乘客,再上来了六位乘客;到了第三站也先下一半乘客,再上来了五位乘客,以后每到一站都先下车上已有的一半乘客,再上来了乘客比前一站少一个……,到了终点站车上还有乘客六人,问发车时车上的乘客有多少? (5)猴子吃桃。有一群猴子摘来了一批桃子,猴王规定每天只准吃一半加一只(即第二天吃剩下的一半加一只,以此类推),第九天正好吃完,问猴子们摘来了多少桃子? (6)小华读书。第一天读了全书的一半加二页,第二天读了剩下的一半加二页,以后天天如此……,第六天读完了最后的三页,问全书有多少页? (7)日本著名数学游戏专家中村义作教授提出这样一个问题:父亲将2520个桔子分给六个儿子。分完后父亲说:“老大将分给你的桔子的1/8给老二;老二拿到后连同原先的桔子分1/7给老三;老三拿到后连同原先的桔子分1/6给老四;老四拿到后连同原先的桔子分1/5给老五;老五拿到后连同原先的桔子分1/4给老六;老六拿到后连同原先的桔子分1/3给老大”。结果大家手中的桔子正好一样多。问六兄弟原来手中各有多少桔子? 四、实验过程 (一)题目一:…… 1.题目分析 由已知可得,运动会最后一天剩余的金牌数gold等于运动会举行的天数由此可倒推每一 天的金牌剩余数,且每天的金牌数应为6的倍数。 2.算法构造 设运动会举行了N天, If(i==N)Gold[i]=N; Else gold[i]=gold[i+1]*7/6+i;

递归算法详解完整版

递归算法详解标准化管理处编码[BBX968T-XBB8968-NNJ668-MM9N]

递归 冯文科一、递归的基本概念。 一个函数、概念或数学结构,如果在其定义或说明内部直接或间接地出现对其本身的引用,或者是为了描述问题的某一状态,必须要用至它的上一状态,而描述上一状态,又必须用到它的上一状态……这种用自己来定义自己的方法,称之为递归或递归定义。在程序设计中,函数直接或间接调用自己,就被称为递归调用。 二、递归的最简单应用:通过各项关系及初值求数列的某一项。 在数学中,有这样一种数列,很难求出它的通项公式,但数列中各项间关系却很简 a与前面临近几项之间的关单,于是人们想出另一种办法来描述这种数列:通过初值及 n 系。 要使用这样的描述方式,至少要提供两个信息:一是最前面几项的数值,一是数列间各项的关系。 比如阶乘数列 1、2、6、24、120、720…… 如果用上面的方式来描述它,应该是: a的值,那么可以很容易地写成这样: 如果需要写一个函数来求 n

这就是递归函数的最简单形式,从中可以明显看出递归函数都有的一个特点:先处理一些特殊情况——这也是递归函数的第一个出口,再处理递归关系——这形成递归函数的第二个出口。 递归函数的执行过程总是先通过递归关系不断地缩小问题的规模,直到简单到可以作为特殊情况处理而得出直接的结果,再通过递归关系逐层返回到原来的数据规模,最终得出问题的解。 以上面求阶乘数列的函数) f为例。如在求)3(f时,由于3不是特殊值,因此需 (n 要计算)2( 3f,但)2(f是对它自己的调用,于是再计算)2(f,2也不是特殊值,需要计 * 算)1( f,返回 )1(= 2f,需要知道)1(f的值,再计算)1(f,1是特殊值,于是直接得出1 * 上一步,得2 3 * )2( )3(= = f,从而得最终 =f )1( 3 2 * * )2(= =f 2 f,再返回上一步,得6 解。 用图解来说明,就是

汉诺塔问题非递归算法详解

Make By Mr.Cai 思路介绍: 首先,可证明,当盘子的个数为n 时,移动的次数应等于2^n - 1。 然后,把三根桩子按一定顺序排成品字型(如:C ..B .A ),再把所有的圆盘按至上而下是从小到大的顺序放在桩子A 上。 接着,根据圆盘的数量确定桩子的排放顺序: 若n 为偶数,按顺时针方向依次摆放C ..B .A ; 若n 为奇数,按顺时针方向依次摆放B ..C .A 。 最后,进行以下步骤即可: (1)首先,按顺时针方向把圆盘1从现在的桩子移动到下一根桩子,即当n 为偶数时,若圆盘1在桩子A ,则把它移动到B ;若圆盘1在桩子B ,则把它移动到C ;若圆盘1在桩子C ,则把它移动到A 。 (2)接着,把另外两根桩子上可以移动的圆盘移动到新的桩子上。 即把非空桩子上的圆盘移动到空桩子上,当两根桩子都非空时,移动较小的圆盘。 (3)重复(1)、(2)操作直至移动次数为2^n - 1。 #include #include using namespace std; #define Cap 64 class Stake //表示每桩子上的情况 { public: Stake(int name,int n) { this->name=name; top=0; s[top]=n+1;/*假设桩子最底部有第n+1个盘子,即s[0]=n+1,这样方便下面进行操作*/ } int Top()//获取栈顶元素 { return s[top];//栈顶 } int Pop()//出栈 { return s[top--];

} void Push(int top)//进栈 { s[++this->top]=top; } void setNext(Stake *p) { next=p; } Stake *getNext()//获取下一个对象的地址 { return next; } int getName()//获取当前桩子的编号 { return name; } private: int s[Cap+1];//表示每根桩子放盘子的最大容量 int top,name; Stake *next; }; void main() { int n; void hanoi(int,int,int,int); cout<<"请输入盘子的数量:"; cin>>n; if(n<1) cout<<"输入的盘子数量错误!!!"<

递归算法详解

递归算法详解 C通过运行时堆栈支持递归函数的实现。递归函数就是直接或间接调用自身的函数。 许多教科书都把计算机阶乘和菲波那契数列用来说明递归,非常不幸我们可爱的著名的老潭老师的《C语言程序设计》一书中就是从阶乘的计算开始的函数递归。导致读过这本经书的同学们,看到阶乘计算第一个想法就是递归。但是在阶乘的计算里,递归并没有提供任何优越之处。在菲波那契数列中,它的效率更是低的非常恐怖。 这里有一个简单的程序,可用于说明递归。程序的目的是把一个整数从二进制形式转换为可打印的字符形式。例如:给出一个值4267,我们需要依次产生字符‘4’,‘2’,‘6’,和‘7’。就如在printf函数中使用了%d格式码,它就会执行类似处理。 我们采用的策略是把这个值反复除以10,并打印各个余数。例如,4267除10的余数是7,但是我们不能直接打印这个余数。我们需要打印的是机器字符集中表示数字‘7’的值。在ASCII码中,字符‘7’的值是55,所以我们需要在余数上加上48来获得正确的字符,但是,使用字符常量而不是整型常量可以提高程序的可移植性。‘0’的ASCII码是48,所以我们用余数加上‘0’,所以有下面的关系: ‘0’+ 0 =‘0’ ‘0’+ 1 =‘1’ ‘0’+ 2 =‘2’ ... 从这些关系中,我们很容易看出在余数上加上‘0’就可以产生对应字符的代码。接着就打印出余数。下一步再取商的值,4267/10等于426。然后用这个值重复上述步骤。 这种处理方法存在的唯一问题是它产生的数字次序正好相反,它们是逆向打印的。所以在我们的程序中使用递归来修正这个问题。 我们这个程序中的函数是递归性质的,因为它包含了一个对自身的调用。乍一看,函数似乎永远不会终止。当函数调用时,它将调用自身,第2次调用还将调用自身,以此类推,似乎永远调用下去。这也是我们在刚接触递归时最想不明白的事情。但是,事实上并不会出现这种情况。 这个程序的递归实现了某种类型的螺旋状while循环。while循环在循环体每次执行时必须取得某种进展,逐步迫近循环终止条件。递归函数也是如此,它在每次递归调用后必须越来越接近某种限制条件。当递归函数符合这个限制条件时,它便不在调用自身。 在程序中,递归函数的限制条件就是变量quotient为零。在每次递归调用之前,我们都把quotient除以10,所以每递归调用一次,它的值就越来越接近零。当它最终变成零时,递归便告终止。 /*接受一个整型值(无符号0,把它转换为字符并打印它,前导零被删除*/

递归算法工作栈的变化详解

通常,一个函数在调用另一个函数之前,要作如下的事情:a)将实在参数,返回地址等信息传递给被调用函数保存; b)为被调用函数的局部变量分配存储区;c)将控制转移到被调函数的入口. 从被调用函数返回调用函数之前,也要做三件事情:a)保存被调函数的计算结果;b)释放被调函数的数据区;c)依照被调函数保存的返回地址将控制转移到调用函数.所有的这些,不论是变量还是地址,本质上来说都是"数据",都是保存在系统所分配的栈中的. ok,到这里已经解决了第一个问题:递归调用时数据都是保存在栈中的,有多少个数据需要保存就要设置多少个栈,而且最重要的一点是:控制所有这些栈的栈顶指针都是相同的,否则无法实现同步. 下面来解决第二个问题:在非递归中,程序如何知道到底要转移到哪个部分继续执行?回到上面说的树的三种遍历方式,抽象出来只有三种操作:访问当前结点,访问左子树,访问右子树.这三种操作的顺序不同,遍历方式也不同.如果我们再抽象一点,对这三种操作再进行一个概括,可以得到:a)访问当前结点:对目前的数据进行一些处理;b)访问左子树:变换当前的数据以进行下一次处理;c)访问右子树:再次变换当前的数据以进行下一次处理(与访问左子树所不同的方式). 下面以先序遍历来说明: void preorder_recursive(Bitree T) /* 先序遍历二叉树的递归算法*/ { if (T) { visit(T); /* 访问当前结点*/ preorder_recursive(T->lchild); /* 访问左子树*/ preorder_recursive(T->rchild); /* 访问右子树*/ } } visit(T)这个操作就是对当前数据进行的处理, preorder_recursive(T->lchild)就是把当前数据变换为它的左子树,访问右子树的操作可以同样理解了. 现在回到我们提出的第二个问题:如何确定转移到哪里继续执行?关键在于一下三个地方:a)确定对当前数据的访问顺序,简单一点说就是确定这个递归程序可以转换为哪种方式遍历的树结构;b)确定这个递归函数转换为递归调用树时的分支是如何划分的,即确定什么是这个递归调用树的"左子树"和"右子树"c)确定这个递归调用树何时返回,即确定什么结点是这个递归调用树的"叶子结点".

04.递归算法讲解

1.用递归法计算n! 【讲解】 递归是算法设计中的一种基本而重要的算法。递归方法即通过函数或过程调用自身将问题转化为本质相同但规模较小的子问题,是分治策略的具体体现。 递归方法具有易于描述、证明简单等优点,在动态规划、贪心算法、回溯法等诸多算法中都有着极为广泛的应用,是许多复杂算法的基础。 递归概述 一个函数在它的函数体内调用它自身称为递归(recursion)调用。是一个过程或函数在其定义或说明中直接或间接调用自身的一种方法,通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。用递归思想写出的程序往往十分简洁易懂。一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。 使用递归要注意以下几点: (1)递归就是在过程或函数里调用自身; (2)在使用递增归策略时,必须有一个明确的递归结束条件,称为递归出口。 例如有函数r如下: int r(int a) { b=r(a?1); return b; } 这个函数是一个递归函数,但是运行该函数将无休止地调用其自身,这显然是不正确的。为了防止递归调用无终止地进行,必须在函数内有终止递归调用的手段。常用的办法是加条件判断,满足某种条件后就不再作递归调用,然后逐层返回。 构造递归方法的关键在于建立递归关系。这里的递归关系可以是递归描述的,也可以是递推描述的。 例4-1 用递归法计算n!。 n!的计算是一个典型的递归问题。使用递归方法来描述程序,十分简单且易于理解。 (1)描述递归关系 递归关系是这样的一种关系。设{U 1,U 2 ,U 3 ,…,U n ,…}是一个序列,如果从某一项k开始, U n 和它之前的若干项之间存在一种只与n有关的关系,这便称为递归关系。 注意到,当n≥1时,n!=n*(n?1)!(n=0时,0!=1),这就是一种递归关系。对于特定的k!,它只与k与(k?1)!有关。 (2)确定递归边界 在步骤1的递归关系中,对大于k的U n 的求解将最终归结为对U k 的求解。这里的U k 称 为递归边界(或递归出口)。在本例中,递归边界为k=0,即0!=1。对于任意给定的N!,程序将最终求解到0!。 确定递归边界十分重要,如果没有确定递归边界,将导致程序无限递归而引起死循环。例如以下程序: #include int f(int x) { return(f(x?1));}

递归算法经典案例

案例一: publicclass Fibonacci { //一列数的规则如下: 1、1、2、3、5、8、13、21、34 ,求第30位数是多少?使用递归实现 publicstaticvoid main(String[] args){ System.out.println(Fribonacci(9)); } publicstaticint Fribonacci(int n){ if(n<=2) return 1; else return Fribonacci(n-1)+Fribonacci(n-2); } } 案例二: publicclass Hanio1 { /* * 汉诺塔(又称河内塔)问题其实是印度的一个古老的传说。开天辟地的神勃拉玛(和中国的盘古差不多的神吧)在一个庙里留下了三根金刚石的棒, * 第一根上面套着64个圆的金片,最大的一个在底下,其余一个比一个小,依次叠上去,庙里的众僧不倦地把它们一个个地从这根棒搬到另一根棒上, * 规定可利用中间的一根棒作为帮助,但每次只能搬一个,而且大的不能放在小的上面。计算结果非常恐怖(移动圆片的次数)18446744073709551615, * 众僧们即便是耗尽毕生精力也不可能完成金片的移动了。 *要求:输入一个正整数n,表示有n个盘片在第一根柱子上。输出操作序列,格式为“移动 t从 x 到y”。每个操作一行,

*表示把x柱子上的编号为t的盘片挪到柱子y上。柱子编号为A,B,C,你要用最少的操作把所有的盘子从A柱子上转移到C柱子上。 */ publicstaticvoid main(String[] args){ int i=3; char a ='A',b='B',c='C'; hanio(i,a,b,c); } publicstaticvoid hanio(int n,char a,char b,char c){ if(n==1) System.out.println("移动"+n+"号盘子从"+a+"到"+c); else{ hanio(n-1,a,c,b);//把上面n-1个盘子从a借助b搬到c System.out.println("移动"+n+"号盘子从"+a+"到"+c);//紧接着直接把n搬动c hanio(n-1,b,a,c);//再把b上的n-1个盘子借助a搬到c } } } 案例三: publicclass Rabbit { /* 古典问题:3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少? 分析:首先我们要明白题目的意思指的是每个月的兔子总对数;假设将兔子分为小中大三种,兔子从出生后三个月后每个月就会生出一对兔子, 那么我们假定第一个月的兔子为小兔子,第二个月为中兔子,第三个月之后就为大兔子,那么第一个月分别有1、0、0,第二个月分别为0、1、0, 第三个月分别为1、0、1,第四个月分别为,1、1、1,第五个月分别为2、1、2,第六个月分别为3、2、3,第七个月分别为5、3、5…… 兔子总数分别为:1、1、2、3、5、8、13…… 于是得出了一个规律,从第三个月起,后面的兔子总数都等于前面两个月的兔子总

C语言经典算法详解

一分而治之算法 分而治之方法与软件设计的模块化方法非常相似。为了解决一个大的问题,可以: 1) 把它分成两个或多个更小的问题; 2) 分别解决每个小问题; 3) 把各小问题的解答组合起来,即可得到原问题的解答。小问题通常与原问题相似,可以递归地使用分而治之策略来解决。下列通过实例加以说明。 例:利用分而治之算法求一个整数数组中的最大值。

练习:[找出伪币] 给你一个装有1 6个硬币的袋子。1 6个硬币中有一个是伪造的,并且那个伪造的硬币比真的硬币要轻一些。你的任务是找出这个伪造的硬币。

二贪心算法 贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,但对范围相当广泛的许多问题他能产生整体最优解或者是整体最优解的近似解。 贪心算法(Greedy algorithm)是一种对某些求最优解问题的更简单、更迅速的设计技术。用贪婪法设计算法的特点是一步一步地进行,常以当前情况为基础根据某个优化测度作最优选择,而不考虑各种可能的整体情况,它省去了为找最优解要穷尽所有可能而必须耗费的大量时间,它采用自顶向下,以迭代的方法做出相继的贪心选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题, 通过每一步贪心选择,可得到问题的一个最优解,虽然每一步上都要保证能获得局部最优解,但由此产生的全局解有时不一定是最优的,所以贪婪法不要回溯。 贪心算法是一种改进了的分级处理方法。其核心是根据题意选取一种量度标准。然后将这多个输入排成这种量度标准所要求的顺序,按这种顺序一次输入一个量。如果这个输入和当前已构成在这种量度意义下的部分最佳解加在一起不能产生一个可行解,则不把此输入加到这部分解中。这种能够得到某种量度意义下最优解的分级处理方法称为贪婪算法。 对于一个给定的问题,往往可能有好几种量度标准。初看起来,这些量度标准似乎都是可取的,但实际上,用其中的大多数量度标准作贪婪

算法设计与分析(详细解析(含源代码))

常用算法设计方法 要使计算机能完成人们预定的工作,首先必须为如何完成预定的工作设计一个算法,然后再根据算法编写程序。计算机程序要对问题的每个对象和处理规则给出正确详尽的描述,其中程序的数据结构和变量用来描述问题的对象,程序结构、函数和语句用来描述问题的算法。算法数据结构是程序的两个重要方面。 算法是问题求解过程的精确描述,一个算法由有限条可完全机械地执行的、有确定结果的指令组成。指令正确地描述了要完成的任务和它们被执行的顺序。计算机按算法指令所描述的顺序执行算法的指令能在有限的步骤内终止,或终止于给出问题的解,或终止于指出问题对此输入数据无解。 通常求解一个问题可能会有多种算法可供选择,选择的主要标准是算法的正确性和可靠性,简单性和易理解性。其次是算法所需要的存储空间少和执行更快等。 算法设计是一件非常困难的工作,经常采用的算法设计技术主要有迭代法、穷举搜索法、递推法、贪婪法、回溯法、分治法、动态规划法等等。另外,为了更简洁的形式设计和藐视算法,在算法设计时又常常采用递归技术,用递归描述算法。 一、迭代法 迭代法是用于求方程或方程组近似根的一种常用的算法设计方法。设方程为f(x)=0,用某种数学方法导出等价的形式x=g(x),然后按以下步骤执行: (1)选一个方程的近似根,赋给变量x0; (2)将x0的值保存于变量x1,然后计算g(x1),并将结果存于变量x0; (3)当x0与x1的差的绝对值还小于指定的精度要求时,重复步骤(2)的计算。 若方程有根,并且用上述方法计算出来的近似根序列收敛,则按上述方法求得的x0就认为是方程的根。上述算法用C程序的形式表示为: 【算法】迭代法求方程的根 { x0=初始近似根; do { x1=x0; x0=g(x1);/*按特定的方程计算新的近似根*/ } while ( fabs(x0-x1)>Epsilon); printf(“方程的近似根是%f\n”,x0); } 迭代算法也常用于求方程组的根,令 X=(x0,x1,…,x n-1) 设方程组为:

汉诺塔C递归算法详细解答

汉诺塔C递归算法详细解答 程序如下: void move(char x,char y){ printf("%c-->%c\n",x,y); } void hanoi(intn,charone,chartwo,char three){ /*将n个盘从one座借助two座,移到three座*/ if(n==1) move(one,three); else{ hanoi(n-1,one,three,two); move(one,three); hanoi(n-1,two,one,three); } } main(){ int n; printf("input the number of diskes:"); scanf("%d",&n); printf("The step to moving %3d diskes:\n",n); hanoi(n,'A','B','C'); } Hanoi塔问题, 算法分析如下,设A上有n个盘子。 如果n=1,则将圆盘从A直接移动到C。 如果n=2,则: (1)将A上的n-1(等于1)个圆盘移到B上; (2)再将A上的一个圆盘移到C上; (3)最后将B上的n-1(等于1)个圆盘移到C上。 如果n=3,则: A)将A上的n-1(等于2,令其为n`)个圆盘移到B(借助于C),步骤如下:(1)将A上的n`-1(等于1)个圆盘移到C上。 (2)将A上的一个圆盘移到B。 (3)将C上的n`-1(等于1)个圆盘移到B。 B)将A上的一个圆盘移到C。 C)将B上的n-1(等于2,令其为n`)个圆盘移到C(借助A),步骤如下:(1)将B上的n`-1(等于1)个圆盘移到A。 (2)将B上的一个盘子移到C。 (3)将A上的n`-1(等于1)个圆盘移到C。到此,完成了三个圆盘的移动过程。

递归讲解

复习 输入a,b,c,计算m 。已知m=) ,,max(),,max(),,max(c b b a c b b a c b a +?+ 请把求三个数的最大数max(x,y,z)定义成函数和过程两种方法作此题。 递 归 为了描述问题的某一状态,必须用到它的上一状态,而描述上一状态,又必须用到它的上一状态……这种用自已来定义自己的方法,称为递归定义。例如:定义函数f(n)为: /n*f(n -1) (n>0) f(n)= | \ 1(n=0) 则当n>0时,须用f(n-1)来定义f(n),用f(n-1-1)来定义f(n-1)……当n=0时,f(n)=1。 由上例我们可看出,递归定义有两个要素: (1) 递归边界条件。也就是所描述问题的最简单情况,它本身不再使用递归的定义。 如上例,当n=0时,f(n)=1,不使用f(n-1)来定义。 (2) 递归定义:使问题向边界条件转化的规则。递归定义必须能使问题越来越简单。 如上例:f(n)由f(n-1)定义,越来越靠近f(0),也即边界条件。最简单的情况是f(0)=1。 递归算法的效率往往很低, 费时和费内存空间. 但是递归也有其长处, 它能使一个蕴含递归关系且结构复杂的程序简介精炼, 增加可读性. 特别是在难于找到从边界到解的全过程的情况下, 如果把问题推进一步使其结果仍维持原问题的关系, 则采用递归算法编程比较合适. 递归按其调用方式分为: 1. 直接递归, 递归过程P 直接自己调用自己; 2. 间接递归, 即P 包含另一过程 D, 而D 又调用P. 递归算法适用的一般场合为: 1. 数据的定义形式按递归定义. 如裴波那契数列的定义: f(n)=f(n-1)+f(n-2); f(0)=1; f(1)=2. 对应的递归程序为: Function fib(n : integer) : integer; Begin if n = 0 then fib := 1 { 递归边界 } else if n = 1 then fib := 2 else fib := fib(n-2) + fib(n-1) { 递归 } End; 这类递归问题可转化为递推算法, 递归边界作为递推的边界条件. 2. 数据之间的关系(即数据结构)按递归定义. 如树的遍历, 图的搜索等. 3. 问题解法按递归算法实现. 例如回溯法等. 从问题的某一种可能出发, 搜索从这种情况出发所能达到的所有可能, 当这一条路走到" 尽头 "的时候, 再倒回出发点, 从另一个可能出发, 继续搜索. 这种不断" 回溯 "寻找解的方法, 称作" 回溯法 ". 例1、给定N (N>=1),用递归的方法计算1+2+3+4+…+(n-1)+n 。 分析与解答 本题是累加问题可以用递归方法求解。本题中,当前和=前一次和+当前项,而前一次和的计算方法与其相同,只是数据不同,即可利用s(n)=s(n-1)+n 来求解,另外递归调用的次数是有限次,且退出的条件是当n=1时s=1,这恰好符合递归算法的使用条件。 程序代码如下: program p_1(input,output); var s,t:integer;

五种算法知识讲解

算法 分治算法 一、基本概念 在计算机科学中,分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)…… 任何一个可以用计算机求解的问题所需的计算时间都与其规模有关。问题的规模越小,越容易直接求解,解题所需的计算时间也越少。例如,对于n个元素的排序问题,当n=1时,不需任何计算。n=2时,只要作一次比较即可排好序。n=3时只要作3次比较即可,…。而当n较大时,问题就不那么容易处理了。要想直接解决一个规模较大的问题,有时是相当困难的。 二、基本思想及策略 分治法的设计思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。 分治策略是:对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。这种算法设计策略叫做分治法。 如果原问题可分割成k个子问题,1

递归算法详解

递归算法详解 Document number:WTWYT-WYWY-BTGTT-YTTYU-2018GT

递归 冯文科一、递归的基本概念。 一个函数、概念或数学结构,如果在其定义或说明内部直接或间接地出现对其本身的引用,或者是为了描述问题的某一状态,必须要用至它的上一状态,而描述上一状态,又必须用到它的上一状态……这种用自己来定义自己的方法,称之为递归或递归定义。在程序设计中,函数直接或间接调用自己,就被称为递归调用。 二、递归的最简单应用:通过各项关系及初值求数列的某一项。 在数学中,有这样一种数列,很难求出它的通项公式,但数列中各项间关系却很 a与前面临近几项之间简单,于是人们想出另一种办法来描述这种数列:通过初值及 n 的关系。 要使用这样的描述方式,至少要提供两个信息:一是最前面几项的数值,一是数列间各项的关系。 比如阶乘数列 1、2、6、24、120、720…… 如果用上面的方式来描述它,应该是: a的值,那么可以很容易地写成这样:如果需要写一个函数来求 n

这就是递归函数的最简单形式,从中可以明显看出递归函数都有的一个特点:先处理一些特殊情况——这也是递归函数的第一个出口,再处理递归关系——这形成递归函数的第二个出口。 递归函数的执行过程总是先通过递归关系不断地缩小问题的规模,直到简单到可以作为特殊情况处理而得出直接的结果,再通过递归关系逐层返回到原来的数据规模,最终得出问题的解。 以上面求阶乘数列的函数) f为例。如在求)3(f时,由于3不是特殊值,因 (n 此需要计算)2( * 3f,但)2(f是对它自己的调用,于是再计算)2(f,2也不是特殊值,需要计算)1( 2f,需要知道)1(f的值,再计算)1(f,1是特殊值,于是直接得出 * 2 )2(= =f f,再返回上一步,得 )1( * 1 )1(= f,返回上一步,得2 )3(= = f,从而得最终解。 3 =f )2( 6 * 2 3 * 用图解来说明,就是Array 下面再看一个稍复杂点的例子。

1-编写一个递归算法将输入的任意一个正整数n以相反的顺序输出

1 编写一个递归算法将输入的任意一个正整数n以相反的顺序输出。例如:输入12345则输出54321。 解析:1)本题考查对函数的定义和调用的掌握情况 2)为了使算法简单,采用递归方法进行程序设计, 程序流程图

2编写程序,要求从键盘任意输入一个3*3的矩阵,利用二维数组的结构将输入存储,并计算该矩阵的对角线之和,并将结果显示出来。 解析:1)采用二维矩阵存储输入的数据 2)利用二重循环控制输入顺序 3)对数组对角线元素的判断是行数等于列数 程序流程

3将从键盘输入一串英文字符中的大写字符转换成小写字符,并输出转换后的字符串,要求使用指针来操作。 显示结果为:Please input char: asf A KFJewrtrOPKs ASFAKFJEWRTROPKS 解析:1)小写字母比大写字母的ASCII码在数值上大32,这是小写转大写的途经2)对输入的字符串采用指针依次读取每一个字符 3)对每个字符判断,若为小写不变,为大写+32转换为小写,若不是字母,显示错误并退出 4)对字符串的输入调用系统函数gets() 程序流程

4 运用数据链表,建立一个学生数据库,包括学生的姓名,学号,年龄,性别,成绩信息。程序提供两个功能,当输入n时,表示输入新的记录,输入l时表示将所有学生数据输出。输入此外的字符则程序终止。 解析: 1在主函数的外部先定义两个指针变量myhead,mythis,mynew用它们来处理链表节点的联系,并在主函数中给head赋值NULL,即链表开始是空的 2如果输入的是n就执行输入新数据,用new_record()函数实现,将一个新节点插入链表中,首先要开辟新节点,就要调用malloc函数,用sizeof(struct stud_type) 来测出每个节点的长度,用new指向开辟的新节点,将新节点链接到表中,再对节点赋值。 3输入l时,将所有的数据输出,用listall()函数实现,从表头开始,依次遍历链表,到表尾时终止

vb汉诺塔的递归算法与解析

汉诺塔的递归算法与解析 从左到右 A B C 柱大盘子 在下, 小盘子在上, 借助B柱将 所有盘子从A柱移动到C柱, 期 间只有一个原则: 大盘子只能 在小盘子的下面. 如果有3个盘子, 大中小号, 越小的越在上面, 从上面给盘子按顺序编号1(小),2(中),3(大), 后面的原理解析引用这里的编号. 小时候玩过这个游戏, 基本上玩到第7个,第8个就很没有耐心玩了,并且操作的动作都几乎相同觉得无聊. 后来学习编程, 认识到递归, 用递归解决汉诺塔的算法也是我除了简单的排序算法后学习到的第一种算法. 至于递归,简单来说就是方法内部自己调用自己, 同时也一定有一个结束点. 如果对方法调用栈了解的话,其实是很容易理解方法的调用过程的, 就是从主线程开始调用方法进行不停的压栈和出栈操作. 方法的调入就是将方法压入栈中, 方法的结束就是方法出栈的过程, 这样保证了方法调用的顺序流. 如果跟踪递归的调用情况会发现也是如此, 到最后一定是这个方法最后从栈中弹出回到主线程, 并且结束.

栈的特点:先进后出。比如一个方法 A 自己调用自己, 我用编号区分一下进栈过程:A -> A(1) -> A(2) -> A(3) 在A(3)时满足某种条件得以退出, 回到A(2), A(2)结束回到A(1), 再回到A, 出栈过程:A(3) -> A(2) -> A(1) -> A 对于递归,还有一个形象的认识,就是我小时候家里有一个柜子, 柜子两端都是玻璃, 头伸进柜子看一面镜子,会看到镜子里还有镜子, 然后镜子里还有镜子, 但和递归的特点不同的是这镜子的反射是没有尽头的, 只要眼睛一直能看到底的话. 了解完递归后, 再回头来看如何用递归的方式解决汉诺塔的问题. 案例 1 - 假设只有一个盘子的时候, 盘子数量N=1 只有一个步骤将第1个盘子从A移动到C, 为了对比方便我这样来描述这个步骤: 步骤盘子编号从柱子移动移动到柱子 1 1 A C 案例 2 - 如果有两个盘子, 盘子数量N = 2 步骤盘子编号从柱子移动移动到柱子 1 1 A B 2 2 A C 3 1 B C 案例 3 - 如果有三个盘子, 盘子数量N = 3 步骤盘子编号从柱子移动移动到柱子 1 1 A C

相关文档
最新文档