递归是算法设计中的一种基本而重要的算法。递归方法即通过函数或过程调用自身将问题转化为本质相同但规模较小的子问题,是分治策略的具体体现。
递归方法具有易于描述、证明简单等优点,在动态规划、贪心算法、回溯法等诸多算法中都有着极为广泛的应用,是许多复杂算法的基础。
一个函数在它的函数体内调用它自身称为递归(recursion)调用。是一个过程或函数在其定义或说明中直接或间接调用自身的一种方法,通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的**量。
递归的能力在于用有限的语句来定义对象的无限集合。用递归思想写出的程序往往十分简洁易懂。 一般来说,递归需要有边界条件、递归前进段和递归返回段。
当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
使用递归要注意以下几点:
1)递归就是在过程或函数里调用自身;
2)在使用递增归策略时,必须有一个明确的递归结束条件,称为递归出口。
例如有函数r如下:
int r(int a)
b=r(a1);
return b;
这个函数是一个递归函数,但是运行该函数将无休止地调用其自身,这显然是不正确的。为了防止递归调用无终止地进行,必须在函数内有终止递归调用的手段。常用的办法是加条件判断,满足某种条件后就不再作递归调用,然后逐层返回。
构造递归方法的关键在于建立递归关系。这里的递归关系可以是递归描述的,也可以是递推描述的。
例4-1 用递归法计算n!。
n!的计算是一个典型的递归问题。使用递归方法来描述程序,十分简单且易于理解。
1)描述递归关系。
递归关系是这样的一种关系。设是一个序列,如果从某一项k开始,un和它之前的若干项之间存在一种只与n有关的关系,这便称为递归关系。
注意到,当n≥1时,n!=n*(n1)!(n=0时,0!=1),这就是一种递归关系。对于特定的k!,它只与k与(k1)!有关。
2)确定递归边界。
在步骤1的递归关系中,对大于k的un的求解将最终归结为对uk的求解。这里的uk称为递归边界(或递归出口)。在本例中,递归边界为k=0,即0!
=1。对于任意给定的n!,程序将最终求解到0!。
确定递归边界十分重要,如果没有确定递归边界,将导致程序无限递归而引起死循环。例如以下程序:
#include <>
int f(int x)
return(f(x1));
main()
printf(f(5));
它没有规定递归边界,运行时将无限循环,会导致错误。
3)写出递归函数并译为**。
将步骤1和步骤2中的递归关系与边界统一起来用数学语言来表示,即。
n!= n*(n1)! 当n>1时。
n!= 1当n=1时。
再将这种关系翻译为**,即一个函数:
long f(int n)
long g;
if(n<0) printf("n<0, 输入错误!")
else if(n==1) g=1;
else g=n*f(n1);
return(g);
4)完善程序。
主要的递归函数已经完成,设计主程序调用递归函数即可。
#include <>
long f(int n)
long g;
if(n<0) printf("n<0,输入错误!")
else if(n==1) g=1;
else g=n*f(n-1);
return(g);
void main()
int n;
long y;
printf(" 计算n!,请输入n: "scanf("%d",&n);
y=f(n);
printf(" d!=%ld ",n,y);
程序中给出的函数f是一个递归函数。主函数调用f后即进入函数f执行,如果n<0,n==0或n=1时都将结束函数的执行,否则就递归调用f函数自身。由于每次递归调用的实参为n1,即把n1的值赋予形参n,最后当n1的值为1时再作递归调用,形参n的值也为1,将使递归终止,然后可逐层退回。
下面我们再举例说明该过程。设执行本程序时输入为5,即求 5!。在主函数中的调用语句即为y=f(5),进入f函数后,由于n=5,不等于0或1,故应执行f=f(n1)*n,即f=f(51)*5。
该语句对f作递归调用即f(4)。
进行4次递归调用后,f函数形参取得的值变为1,故不再继续递归调用而开始逐层返回主调函数。f(1)的函数返回值为1,f(2)的返回值为1*2=2,f(3)的返回值为2*3=6,f(4) 的返回值为6*4=24,最后返回值f(5)为24*5=120。
综上,得出构造一个递归方法基本步骤,即描述递归关系、确定递归边界、写出递归函数并译为**,最后将程序完善。
例4-2 计算阿克曼函数
阿克曼(ackerman)函数a(n,m)递归定义如下:
试输出阿克曼函数的(m≤3,n≤10)的值。
解:a函数是一个随着变量m,n变化的双递归函数。
当m=0时,a(0,n)=n+1,这是递归终止条件;
当n=0时,a(m,0)=a(m-1,1);这是n=0时的递归表达式。
当m,n≥1时,a(m,n)=a(m-1,a(m,n-1)),这是递归表达式。
试以a(1,3)为例说明函数的递归过程:
a(1,3)=a(0,a(1,2))=a(0,a(0,a(1,1)))a(0,a(0,a(0,a(1,0)))
= a(0,a(0,a(0,a(0,1)))a(0,a(0,a(0,2)))
= a(0,a(0,3))=a(0,4)=5
a函数及其调用描述如下:
int a(int m,int n)
printf("");
程序运行求示例:
a(m,n) n=0 n=1 n=2 n=3 n=4 n=5 n=6 n=7 n=8 n=9 n=10
m=0 1 2 3 4 5 6 7 8 9 10 11
m=1 2 3 4 5 6 7 8 9 10 11 12
m=2 3 5 7 9 11 13 15 17 19 21 23
m=3 5 13 29 61 125 253 509 1021 2045 4093 8189
若采用递推求a(3,10),由上表可知a(3,9)=4093,则。
a(3,10)=a(2,a(3,9))=a(2,4093)
n的取值是未知的且非常大,可见递推完成的难度。
1. 问题提出。
一场球赛开始前,售票工作正在紧张的进行中。每张球票为50元,现有30个人排队等待购票,其中有20 个人手持50元的钞票,另外10个人手持100元的钞票。假设开始售票时售票处没有零钱,求出这30个人排队购票,使售票处不至出现找不开钱的局面的不同排队种数。
(约定:拿同样面值钞票的人对换位置后为同一种排队。)
2.递归设计要点。
我们考虑一般情形:有m+n个人排队等待购票,其中有m 个人手持50元的钞票,另外n个人手持100元的钞票。求出这m+n个人排队购票,使售票处不至出现找不开钱的局面的不同排队种数(这里正整数m,n从键盘输入)。
这是一道典型的组合计数问题,考虑用递推求解。
令f(m,n)表示有m个人手持50元的钞票,n个人手持100元的钞票时共有的方案总数。我们分情况来讨论这个问题。
1) n=0
n=0意味着排队购票的所有人手中拿的都是50元的钱币,注意到拿同样面值钞票的人对换位置后为同一种排队,那么这m个人的排队总数为1,即f(m,0)=1。
2)m当m (3)其它情况。
我们思考m+n个人排队购票,第m+n个人站在第m+n-1个人的后面,则第m+n个人的排队方式可由下列两种情况获得:
1) 第m+n个人手持100元的钞票,则在他之前的m+n-1个人中有m个人手持50元的钞票,有n-1个人手持100元的钞票,此种情况共有f(m,n-1)。
2) 第m+n个人手持50元的钞票,则在他之前的m+n-1个人中有m-1个人手持50元的钞票,有n个人手持100元的钞票,此种情况共有f(m-1,n)。
由加法原理得到f(m,n)的递推关系:
f(m,n)=f(m,n-1)+f(m-1,n)
初始条件:当m当n=0时,f(m,n)=1
3. 购票排队递归程序实现。
/ 购票排队
long f(int j,int i)
long y;
if(i==0) y=1;
else if(j else y=f(j-1,i)+f(j,i-1); 实施递归
return(y);
#include<>
void main()
int m,n;
printf(" input m,n: "scanf("%d,%d",&m,&n);
printf(" f(%d,%d)=%ld.",m,n,f(m,n));
实验第4讲
实验四matlab数值计算。一 实验目的。1.掌握线性方程组的求解方法。2.掌握数值插值与曲线拟合的方法及应用3.掌握求数值导数和数值积分的方法4.掌握非线性方程组的求解方法。二 实验内容。1.求解线性方程组2.线性插值与曲线拟合3.求数值导数和数值积分4.非线性方程组的数值解。三 实验过程。1.求...
第4讲概述
关于两个管理层次在工程建设各个阶段的咨询业务,表1 2进行了对比 项目管理层次阶段性工作和工程咨询业务关系表。2007年真题 项目执行管理层次委托的融资咨询是项目周期中 的工作。实施阶段 b 完工阶段 c 准备阶段 d 策划阶段。答案 c2007年真题 在项目实施阶段为项目决策管理层次提供的工程咨询...
第4讲概述
关于两个管理层次在工程建设各个阶段的咨询业务,表1 2进行了对比 项目管理层次阶段性工作和工程咨询业务关系表 重点每年都考 2007年真题 项目执行管理层次委托的融资咨询是项目周期中 的工作。实施阶段 b 完工阶段 c 准备阶段 d 策划阶段。答案 c2007年真题 在项目实施阶段为项目决策管理层次...