版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
7.1递归
递归(recursion)是计算科学领域的一种重要的思维模式,是一种问题求解的方法。例如,数学公式n!=1×2×···×n的计算可以取n-1次乘法的结果,也可以采用另一种思维方法———n!=n(n-1)!。也就是说,给定n,则n!可由(n-1)!求出,(n-1)!可由(n-2)!求出,···,2!可由1!求出。而1!=1已知,那么2!可求得,
从而3!可求得,
依次类推,
n!可求得。
这种思维方式便是一种递归。
递归思维将复杂的计算简化为简单计算的不断重复,是最重要的计算思维。在C/C++语言中,递归的实现是采用函数的自我调用来实现。函数直接
(或间接)调用自己就称为函数的递归调用,这种函数就称为递归函数。递归函数将反复调用其自身,每调用一次就进入新的一层,当最内层的函数执行完毕后,
就一层一层地由里到外返回。
调用自身的过程又称为“递推”,由里到外返回的过程又称为“回归”,所以递归过程可以分为“递推”和“回归”两个阶段。下一页返回7.1递归
本节以一个简单示例为基点,阐述应用递归的程序设计过程。【例7-1】用递归函数编写计算阶乘n!的函数factorial。【分析】阶乘n!的计算公式如下:根据上面阶乘的计算公式可以发现,阶乘的计算公式是一个递归计算公式。为了计算n!,
需要调用计算阶乘的函数factorial(n),
它因要计算(n-1)!,
而调用factorial(n-1),于是形成递归调用。
这个过程一直持续到1!为止。
在求得1!=1以后,
逐层返回,
最后求得n!。上一页下一页返回7.1递归
算法的伪代码如下:(1)输入一个自然数,
存入变量n;(2)递归调用函数factorial;(3)输出n!的值;上一页下一页返回7.1递归
7.1.1
递归的思想递归是程序设计中的一种常见方法。采用递归方法编写程序可以使程序更加简洁、清晰、
容易理解。
阶乘问题是一个经典的数学递归问题。
为了得到n!,
操作步骤如下(注意参数的变化):(1)第1次调用函数factorial(n)。
根据公式可知:n!=n×(n-1)!。所以,
需要知道(n-1)!的数值,
那么就需要第2次调用函数factorial。(2)第2次调用函数factorial(n-1)。
根据公式可知:(n-1)!=(n-1)×(n-2)!。所以,需要知道(n-2)!的数值,
那么就需要第3次调用函数factorial。(3)第3次调用函数factorial(n-2)。
根据公式可知:(n-2)!=(n-2)×(n-3)!。所以,需要知道(n-3)!的数值,
那么就需要第4次调用函数factorial。上一页下一页返回7.1递归
直到调用函数factorial(2),
都可采用同样的方法进行处理,
2!=2×1!。
根据公式可知:1!=1。
因此,
问题最终可以求解。可以看出,每次面对的问题在本质上是同一个问题,只是问题的规模不同。上面的解决方法体现了递归的思想,将复杂问题分解为规模较小的子问题,
且子问题和原问题本质上是相同的问题。在将子问题求解后,原问题也将求解。接下来,
以求解5!为例,
说明递归的递推过程和回归过程。上一页下一页返回7.1递归
7.1.2
递归的递推求解5!的递推过程如下:(1)求解5!,
即调用factorial(5)。
当进入factorial()函数体后,
由于形参n的值为5,不等于1,
所以执行factorial(n-1)∗n,
即执行factorial(4)∗5。
为了求得这个表达式的结果,
必须先调用factorial(4),
并暂停其他操作。
换言之,
在得到factorial(4)的结果之前,不能进行其他操作。(2)调用factorial(4)时,
实参为4,
形参n
也为4,
不等于1,
因此将继续执行factorial(n-1)∗n,
即执行factorial(3)∗4。
为了求得这个表达式的结果,
必须先调用facto-rial(3)。上一页下一页返回7.1递归
(3)调用factorial(3)时,
实参为3,
形参n
也为3,
不等于1,
因此将继续执行factorial(n-1)∗n,
也即执行factorial(2)∗3。
为了求得这个表达式的结果,
又必须先调用factorial(2)。(4)调用factorial(2)时,
实参为2,
形参n也为2,
不等于1,
因此将继续执行factorial(n-1)∗n,
即执行factorial(1)∗2。
为了求得这个表达式的结果,
必须先调用facto-rial(1)。(5)在进行4次调用后,
实参的值为1,
因此将调用factorial(1)。
此时,
能够直接得到常量为1的值,
并把结果返回,
不需要再次调用factorial()函数,
递归结束。表7-1列出了在递归调用过程中逐层进入的递推过程。上一页下一页返回7.1递归
7.1.3递归的回归当递归进入最内层时,就结束递归,开始逐层退出,即逐层执行return语句,这就是递归的回归过程。
求解5!的回归过程如下:(1)当n的值为1时,
达到最内层,
此时return的结果为1,
即factorial(1)的调用结果为1。(2)有了factorial(1)的结果,
就可以返回上一层,
计算factorial(1)∗2的值。
此时,得到的值为2,
return的结果也为2,
即factorial(2)的调用结果为2。(3)有了factorial(2)的结果,
就可以返回上一层,
计算factorial(2)∗3的值。
此时,得到的值为6,
return的结果也为6,
即factorial(3)的调用结果为6。上一页下一页返回7.1递归
(4)有了factorial(3)的结果,
就可以返回上一层,
计算factorial(3)∗4的值。
此时,得到的值为24,
return的结果也为24,
即factorial(4)的调用结果为24。(5)有了factorial(4)的结果,
就可以返回上一层,
计算factorial(4)∗5的值。
此时,得到的值为120,
return的结果也为120,
即factorial(5)的调用结果为120。
这样就得到了5!的值。表7-2列出了在递归调用过程中逐层退出的回归过程。上一页下一页返回7.1递归
7.1.4递归的条件每一个递归函数都应该只进行有限次递归调用,否则就会进入死胡同,
永远也不能退出,这样的程序是没有意义的。要想让递归函数逐层进入再逐层退出,需要解决以下两方面的问题:(1)存在限制条件,
当符合该条件时,
递归便不再继续。
对于函数factorial,
当形参n等于1时,递归就结束。这种限制条件就是递归结束条件,称为递归出口。(2)每次递归调用后,
越来越接近该限制条件。
对于函数factorial,
每次递归调用的实参为n-1,这会使形参n的值逐渐减小,越来越趋近于1。上一页下一页返回7.1递归
7.1.5
递归的实现经过上面递归调用的分析,例7-1的程序代码如下:上一页下一页返回7.1递归
上一页下一页返回7.1递归
【练习】(1)用递归方法求1~100的和,
即1+2+3+4+···+100。提示:递归求和公式为(2)用递归方法求1~100的连乘积,
即1×2×3×4×···×100。提示:递归求连乘积公式为(3)从1,2,3,···,n中取出m个数,一共有多少种选择方案?提示:计算从n个数中取m个数的组合数的公式为上一页返回7.2迭代与递归
在计算机编程实现中,
常常有两种编程思想:迭代(iterate);
递归(recursion)。
从概念上讲,递归是指程序调用自身的编程思想,即一个函数调用本身;迭代是利用已知的变量值,根据递推公式不断演进得到变量新值的编程思想。简而言之,递归将大问题化为相同结构的小问题,从待求解的问题出发,一直分解到已知答案的最小问题为止,然后逐级返回,从而得到大问题的解;而迭代则从已知值出发,通过递推式,不断更新变量新值,直到能够解决问题为止。下面以斐波那契数列的求解为例,介绍这两种典型编程思想的实现,并分析二者的区别与联系。下一页返回7.2迭代与递归
【例7-2】斐波那契数列(又称为黄金分割数列,
通常记做Fibonacci)指的是这样一个数列:1,1,2,3,5,8,13,21,···这个数列从第3项开始,每一项都等于前两项之和。该数列刚好和一个有趣的兔子问题相关。一般而言,兔子在出生两个月后,就有繁殖能力,一对兔子每个月能生出一对小兔子来。如果所有兔子都不死,那么若干个月以后的兔子对数为:1,1,2,3,5,8,13,31,···【分析】如果在数列的前面加上数字“0”,那么从第2项开始,每一项都是该项前两项的数字之和。
数列的通项表达式F(n)如下:上一页下一页返回7.2迭代与递归
7.2.1
递归实现可以采用递归的编程方式求解例7-2的问题,方法是定义一个函数Fib,返回数列第n项的值。将规模为n的问题分解为更小规模的问题,即规模为n-1的问题与规模为n-2的问题,
一直分解到规模为0和规模为1的问题。
数列的通项表达式Fib(n)如下所示:例7-2递归函数的伪代码如下:(1)如果函数形参是0,
则函数返回0,
并回归上一层;(2)如果函数形参是1,
则函数返回1,
并回归上一层;(3)如果函数形参大于1,
则继续递归调用函数。上一页下一页返回7.2迭代与递归
将例7-2采用递归求解的函数Fib的代码如下:上一页下一页返回7.2迭代与递归
7.2.2
迭代实现例7-2也可以采用迭代的方式求解。同样,需要定义一个函数Fib,但不将问题分解,而是直接利用已知的变量值,根据递推公式不断演进。由例7-2的递推公式可知,已知两个初值(即数列第0项的值和数列第1项的值),那么根据递推公式,就可以得到第2项的值,,得到第n项的值。算法的伪代码如下:(1)定义两个变量f0和f1,
初值分别为0和1;(2)如果函数形参n是0,
则函数返回0;(3)如果函数形参n是1,
则函数返回1;(4)循环变量i初始化为2,
当循环变量i≤n时,(4.1)计算数列下一项的值,
f=f0+f1;(4.2)更新变量,
f0=f1,
f1=f;(43)i++;(5)函数返回f的值。上一页下一页返回7.2迭代与递归
将例7-2采用迭代求解的函数Fib代码如下:上一页下一页返回7.2迭代与递归
上一页下一页返回7.2迭代与递归
【练习】(1)改写上述迭代算法,
将一维数组作为Fib函数的形参,
把前n项斐波那契数列存入数组,并编写main函数,输出斐波那契数列前100项的数值,输出方式为每行10项。(2)将715节练习1和练习2的递归程序改写为迭代程序。(3)求x平方根近似值的迭代公式为xn
+
1=(xn+x/xn
)/2。
当n为1时,
x1为x,
迭代一次求得的平方根近似值为x2;n为2时,求得的近似值为x3,依次类推。输入正整数x和整数n(n≥0,
且x和n都不会出现溢出情况)。
求利用上述公式,
求x迭代n次后的平方根近似值,保留计算结果小数点后5位有效数字。例如,输入16和4,
则输出400226。上一页下一页返回7.2迭代与递归
7.2.3
递归与迭代的关系递归,实际上就是不断地深层调用函数,直到函数有返回值才会逐层返回。因此,
递归涉及运行时的堆栈开销(参数必须压入堆栈保存,直到该层函数调用返回为止),有可能导致堆栈溢出的错误。但是,递归编程所体现的思想正是人们追求简洁、将问题交给计算机,以及将大问题分解为相同小问题从而解决大问题的动机。迭代在大部分时候需要人为地对问题进行剖析,
将问题转变为一次次的递推来逼近答案。迭代不像递归一样对堆栈有一定要求,一旦问题剖析完毕,就可以很容易地通过循环加以实现。在理论上,递归和迭代在时间复杂度方面是等价的(暂不考虑函数调用开销和函数调用产生的堆栈开销),但实际上,递归的效率比迭代低。既然递归没有任何优势,
那么是不是就没有使用递归的必要了?递归的存在有何意义呢?上一页下一页返回7.2迭代与递归
从理论上说,所有的递归函数都可以转换为迭代函数,反之亦然,然而代价通常都比较高。但是,就算法结构而言,递归声明的结构并不总能转换为迭代结构;就实际而言,所有的迭代都可以转换为递归,但递归不一定可以转换为迭代。迭代虽然效率高,运行时间只因循环次数增加而增加,没什么额外开销,空间上也没有什么增加,但其代码不易理解,
在编写复杂问题时,算法实现较为困难。上一页返回7.3实训与实训指导
实训1
走台阶有个人刚刚看完电影«第39级台阶»,离开电影院时,他数了数礼堂前的台阶数,恰好是39级!站在台阶前,他想到一个问题:如果我每一步只能迈上1个或2个台阶,先迈左脚,然后左右交替,最后一步迈右脚,也就是说一共要走偶数步。那么,上完39级台阶,有多少种不同的上法呢?1实训分析为了统计走完39级台阶的方法,设结果变量result表示上台阶的方法数,初始值是0。由于每走到一级台阶,都需要统计步数,所以设变量num表示台阶,step表示走到num级台阶的步数。题目中说明每一步只能迈上1个或2个台阶,所以第step+1步有可能到达num+1级台阶,也有可能到达num+2级台阶。第step+2步也是同样的情况,依次类推。这里也就是程序的递归。那么当num=39时,表示此时走完39级台阶。下一页返回7.3实训与实训指导
由于题目中要求先迈左脚,然后左右交替,最后一步迈右脚,也就是说一共要走偶数步,所以当num=39时,还需要判断当前的步数是否为偶数步,即“step%2==0”是否成立。如果满足偶数步,则表示一种方案成立,即result++;如果不满足偶数步,表示这种走法不符合要求,舍弃。算法的伪代码如下:(1)设置递归的初始条件,
第num=0级台阶,
共走step=0步;(2)处理递归的下一步,
对于step+1级台阶,
num-2或者num-1,
判断递归是否结束;(3)输出结果。上一页下一页返回7.3实训与实训指导
程序的代码如下:上一页下一页返回7.3实训与实训指导
上一页下一页返回7.3实训与实训指导
上一页下一页返回7.3实训与实训指导
2实训练习编写程序递归实现某个自然数n的k次幂。提示:递归公式为上一页下一页返回7.3实训与实训指导
实训2
换汽水已知1元可以买1瓶汽水,3个瓶盖可以换1瓶汽水,2个空瓶也可以换1瓶汽水。那么,n元总共可以买到多少瓶汽水?1实训分析不妨设变量money、water分别表示钱的金额数和当前的汽水数量。题目中说明1元可以买1瓶汽水,所以汽水的最初数量等于钱的金额大小,即water=money。另外,设变量cap、bottle分别表示当前的瓶盖数量和空瓶数量。由题目可知,3个瓶盖可以换1瓶汽水,2个空瓶也可以换1瓶汽水,所以此时瓶盖和空瓶可以兑换的汽水数量分别为cap/3、bottle/2。因此,汽水的数量由两部分组成,即之前的汽水数量和兑换的汽水数量。上一页下一页返回7.3实训与实训指导
兑换汽水后,
瓶盖和空瓶的数量分别是cap%3、bottle%2,可以用来兑换的汽水数量是cap/3+bottle/2。如此便可以进行下一次兑换,这就是该题的递归部分。直到可兑换的汽水数量等于0时,递归结束。算法的伪代码如下:(1)根据1元买1瓶汽水的原则,
可知初始汽水的数量water=money;(2)初始化递归的条件,
可以用来兑换的汽水数量:water=money,
cap=0,
bottle=0;(3)兑换后,
更新cap、
bottle的数值,
并判断是否有下一次兑换的可能性。
如果还可以兑换,则继续递归;否则,退出递归。上一页下一页返回7.3实训与实训指导
程序的代码如下:上一页下一页返回7.3实训与实训指导
上一页下一页返回7.3实训与实训指导
2实训练习编写程序,利用递归公式求函数值:上一页下一页返回7.3实训与实训指导
实训3
排列数从n个不同元素中任取m(m≤n)个元素,
按照一定的顺序排列起来。
当m=n时,
所有的排列情况称为全排列。例如,1、2、3三个元素的全排列如下:1,2,31,3,22,1,32,3,13,1,23,2,1上一页下一页返回7.3实训与实训指导
1实训分析常用的全排列方法有递归算法和非递归算法。其中,递归算法有四种:字典序法、递增进位制数法、递减进位制数法和邻位对换法。字典序法的排列规则:对给定的字符集中的字符规定一个先后关系,在此基础上规定两个全排列的先后是从左到右逐个比较对应的字符的先后。
例如,
对于字符集{1,2,3},
要求将较小的数字排序较先,
这样按字典序法生成的全排列是:123,132,213,231,312,321。
递归算法由函数Perm(intlist[],intk,intm)实现,将list的前k个元素作为前缀、剩下的m-k个元素进行全排列而得到排列。其想法是将第k个元素与后面的每个元素进行交换,求出其全排列。上一页下一页返回7.3实训与实训指导
算法的伪代码如下:(1)读入参与全排列的数据个数以及具体的数值;(2)从左向右逐个与后面的元素进行交换;(3)递归上述过程,
直到得到n个数,
得到一个全排列;(4)为了下一个全排列,
需要把刚才发生交换的两个数值恢复原状态。
然后,
继续2和3,寻找下一种全排列。上一页下一页返回7.3实训与实训指导
程序的代码如下:上一页下一页返回7.3实训与实训指导
上一页下一页返回7.3实训与实训指导
上一页下一页返回7.3实训与实训指导
上一页下一页返回7.3实训与实训指导
2实训练习A~I代表1~9的数字,不同的字母代表不同的数字。字母A~I组成了下面的算式:某些数字的排列刚好使该式成立。例如,6+8/3+952/714就是一种解法,5+3/1+972/486是另一种解法。这个算式一共有多少种解法?提示:考虑1~9的每种全排列能否使该式成立即可。上一页下一页返回7.3实训与实训指导
实训4
汉诺塔汉诺塔(HanoiTower)又称河内塔,
已知有三根柱子,
在一根柱子上从下往上按照大小顺序摞着64个圆盘。现在需要把圆盘按大小顺序重新摆放在另一根柱子上。规定:任何时候,在小圆盘上都不能摆放大圆盘,且在三根柱子之间一次只能移动一个圆盘。如果将三根柱子分别命名为A、B、C,A柱子上有n片黄金圆盘,应该如何操作才能实现该任务?编写程序输出圆盘移动的操作步骤。要求从键盘接收圆盘的数量。上一页下一页返回7.3实训与实训指导
1实训分析对于这样一个问题,我们很难直接给出圆盘移动的顺序。但是,可以先观察圆盘数量较少时的规律,进而解决这个问题。假设A柱子上的n个圆盘从上至下的编号依次为1、2、···、n,规定将圆盘从A柱子直接移动到C柱子上的过程记为A→C,则:(1)当n=1时,第1次移动:1号圆盘A→C。(2)当n=2时,第1次移动:1号圆盘A→B;第2次移动:2号圆盘A→C;第3次移动:1号圆盘B→C。可以看出,第1次移动将2号圆盘上的1号圆盘从A柱子移到B柱子上。第2次移动将2号圆盘从A柱子移到C柱子上。第3次移动除将2号圆盘外的1号圆盘从B柱子移动到C柱子上。上一
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年可信AI系统真题专项练习测试卷附答案
- (新)中小学教师高级职称专业水平能力试题库(含答案)
- 护士职称考试试题及答案
- 2025年金融风险管理师专业技能考试试卷及答案解析
- 2025年监理工程师之土木建筑目标控制真题练习试卷B卷附答案
- 施工员考试题库及答案解析大全
- 2025年大学(家政学)家庭服务技能综合测试试题及答案
- 省建筑工程总公司职工大学单招职业倾向性测试题库附答案详解
- 2025年无导游证领队人员考试模拟试题及答案
- 四川省自考eda试题及答案
- 《国家基层高血压防治管理指南2025版》解读 2
- 实施指南(2025)《HG-T 6214-2023 邻氨基苯酚》
- 安全生产相关工作主要业绩及研究成果
- 2025广西百色能源投资发展集团有限公司招聘7人(第一批)笔试历年参考题库附带答案详解
- 供热安全培训课件
- 供水管网抢修课件
- 穿越机组装教学课件
- 运输公司安全领导小组会议记录内容
- 7.2动物的特征及类群①课件-沪教版生物七年级下册
- 尼康相机D200中文说明书
- 华电合同管理办法
评论
0/150
提交评论