[理学]第08章 递归与搜索上.ppt_第1页
[理学]第08章 递归与搜索上.ppt_第2页
[理学]第08章 递归与搜索上.ppt_第3页
[理学]第08章 递归与搜索上.ppt_第4页
[理学]第08章 递归与搜索上.ppt_第5页
已阅读5页,还剩34页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第8章 递归与搜索(上),第8章 递归与搜索,递归是一种重要的算法思想。 递归既可以实现递推过程,也可以实现求解诸多问题的通用思路搜索。,8.1 递归的基本思想,8.1.1 什么是递归,在数学上,求n的阶乘,有两种表示方法: n!= n(n-1)(n-2)21 n!= n(n-1)!,这两种表示方法实际上对应到两种不同的算法思想。 第种表示方法中,求n!要反复把1、2、3、(n-2)、(n-1)、n累乘起来,是循环的思想,要用循环结构来实现,代码如下:,int n, F=1; scanf( “%d“, ,第种表示方法,求n!时需要用到(n-1)!。如果有一个函数Factorial( int n )能实现求n的阶乘,则该函数在求n!时要使用到表达式:n*Factorial(n-1),Factorial(n-1)表示调用Factorial( )函数去求(n-1)!。具体代码例8.1。,例8.1 递归求阶乘,#include int Factorial( int n ) if( n0 ) return -1; else if( n=0 | n=1 ) return 1; else return n*Factorial(n-1); /递归调用Factorial函数 int main( ) int A; scanf( “%d“, ,该程序的运行示例如下: 4 4!=24,int Factorial( int n ) if( n0 ) return -1; else if( n=0 | n=1 ) return 1; else return n*Factorial(n-1); ,Factorial( )函数有一个特点,它在执行过程中又调用了Factorial( )函数,这种函数称为递归函数。 具体来说,在执行一个函数过程中,又直接或间接地调用该函数本身,如图8.1所示,这种函数调用称为递归调用;包含递归调用的函数称为递归函数。,假设要求3!,其完整的执行过程如图8.2所示,具体过程为: 执行main函数的开头部分; 当执行到Factorial函数调用“Factorial(3)”时,流程转而去执行Factorial(3)函数,并将实参3传递给形参n; 执行Factorial(3)函数的开头部分; 当执行到递归调用Factorial(n-1)函数时,此时n-1=2,所以要转而去执行Factorial(2)函数; 执行Factorial(2)函数的开头部分; 当执行到递归调用Factorial(n-1)函数时,此时n-1=1,所以要转而去执行Factorial(1)函数;,执行Factorial(1)函数,此时因为n的值为1,所以返回1,而不是再递归调用下去,即,Factorial(1)函数执行完毕,返回到上一层,即返回Factorial(2)函数中; 执行完Factorial(2)函数的剩余语句,返回到Factorial(3)函数中; 执行完Factorial(3)函数的剩余语句,返回到main函数中; 继续执行main函数的剩余部分直到整个程序执行完毕。,8.1.2 递归例题解析,例8.2 猴子吃桃问题。猴子第1天摘下若干个桃子,当即吃了一半,还不过瘾,又多吃了一个。第2天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半另加一个。到第10天早上想再吃时,就只剩下一个桃子了。求第1天共摘了多少个桃子。,分析: 假设Ai为第i天吃完后剩下的桃子的个数,A0表示第一天共摘下的桃子,本题要求的是A0。有以下递推式子: A0 = 2(A1+1) A1:第1天吃完后剩下的桃子数 A1 = 2(A2+1) A2:第2天吃完后剩下的桃子数 A8 = 2(A9+1) A9:第9天吃完后剩下的桃子数 A9 = 1 以上递推过程可分别用循环结构和递归函数实现。,用循环结构实现: 如果x1,x2表示前后两天吃完后剩下的桃子数,则有递推关系:x1 = (x2+1)*2。从第9天剩下1个桃子,反复递推9次,则可求第1天共摘下的桃子数。这里包含了反复的思想,可以用循环结构来实现,代码如下:,#include int main( ) int day, x1, x2; day = 9; x2 = 1; while( day0 ) x1 = (x2+1)*2; / 第1天的桃子数是第2天桃子数加1后的2倍 x2 = x1; day-; printf( “total=%dn“, x1 ); return 0; ,该程序的输出结果: total=1534,用递归思想实现: 前面所述的递推关系也可以采用下面的方式描述。假设第n天吃完后剩下的桃子数为A(n),第n+1天吃完后剩下的桃子数为A(n+1),则存在的推关系:A(n) = ( A(n+1) + 1 ) * 2。这种递推关系可以用递归函数实现,代码如下:,#include int A(int n) if(n=9) return 1; else return(2*(A(n+1)+1); int main( ) printf( “total=%dn“, A(0) ); return 0; ,该程序的输出结果: total=1534,思考:以上递推关系式存在什么特点,为什么可以用递归函数实现?,例8.3 采用递归思想递推Fibonacci数列中的每一项,并输出前20项的值。,分析: Fibonacci数列各项的递推关系是:F(n) = F(n-1) + F(n-2),这种递推关系式很适合用递归函数实现。代码如下:,#include int Fibonacci( int n ) if( n=0 | n=1 ) return 1; else return ( Fibonacci(n-1) + Fibonacci(n-2) ); void main( ) int i; int f20 = 0 ; for( i=0; i20; i+ ) /调用递归函数求每项 fi = Fibonacci(i); for( i=0; i20; i+ ) if( i%10=0 ) printf( “n“ ); /每行输出10个数据 printf( “%6d“, fi ); /每个数据占6个字符的宽度 printf( “n“ ); ,该程序的输出结果: (空一行) 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765,例8.4 输入两个正整数,求其最大公约数,分析: 数论中有一个求最大公约数的算法称为辗转相除法,又称欧几里德(Euclid)算法。其基本思想及执行过程为(设m为两正整数中较大者,n为较小者): (1) 令u = m,v = n; (2) 取u对v的余数,即r = u%v,如果r的值为0,则此时v的值就是m和n的最大公约数,否则执行第(3)步; (3) u = v,v = r,即u的值为v的值,而v的值为余数r。并转向第(2)步。,思考:在初等数学里是如何求两个数的最大公约数?,分析(continue): 例如,假设输入的两个正整数为18和33,则m = 33,n = 18。辗转相除法求最大公约数的过程如下图所示。,r=u%v=15 u=v=18 v=r=15,r=u%v=3 u=v=15 v=r=3,u=m=33 v=n=18,因此18和33的最大公约数是v=3,r=u%v=0,辗转相除法可以采用非递归的方法,即循环方法实现,如下面的代码:,#include int gcd( int u, int v ) /求u和v的最大公约数 int r; while( (r=u%v)!=0 ) u=v; v=r; return(v); int main( ) int m, n, t; printf( “Please input two positive integers : “ ); scanf( “%d%d“, ,该程序的运行示例如下: Please input two positive integers : 18 33 the great common divisor of these two integers is 3.,辗转相除法的思想也可以采用递归方法实现。其递归思想是:在求最大公约数过程中,如果u对v取余的结果为0,则最大公约数就是v;否则递归求v和u%v的最大公约数。因此,上述代码中的gcd函数可改写成:,int gcd( int u, int v ) /求u和v的最大公约数 if( u%v=0 ) return v; else return gcd(v, u%v); ,在使用上述递归gcd函数求“gcd(33,18)”时,要递归调用“gcd(18,15)”;在执行“gcd(18,15)”时又递归调用“gcd(15,3)”;而在执行“gcd(15,3)”时,因为15%3的结果为0,所以最终求得的最大公约数为3。,A,B,C,?,例8.5 经典的Hanoi(汉诺塔)问题 问题描述:古代有一个梵塔,塔内有A,B,C三个柱子。起初,A柱上有n个盘子,依次由大至小、从下往上堆放;要求把它们全部移到C柱上,在移动过程中可以利用B柱,但每次只能移动一个盘子,且必须使三个座始终保持大盘在下,小盘在上的状态。要求编程输出移动的步骤。,解题思路 如果盘子数量很少,通过心算便可以直接想出移动盘子的具体步骤。 比如:只有 2 个盘子的情况,此问题就变得相当的简单,只需要 3 步就可以完成整个移动操作。 AB (表示将 A柱 最上面的 1 个盘子移到 B柱 上) AC (将 A 此时最上面的 1 个盘子移到 C 上) BC (将 B 上的盘子移到 C 上) 又如:移动 3 个盘子的情况,需要 7 步 AC AB CB AC BA BC AC,思考:请尝试写出当盘子数 n=5时具体的移动的步骤。,思考:n个盘子至少需要移动多少步?比如n=64。,现在我想不出移完64个盘子的步骤,但如果有另外一个人能想出怎样移完63个盘子,我只要移最后1个盘子就可以了!,第一个人移动64个盘子(AC)的过程: 命令第 2 个人将 63 个盘子从 A 移到B 上; 自己将剩余的最底下最大的那 1 个盘子从 A 移到 C 上; 再命令第 2 个人将 63 个盘子从 B 移到 C 上; 完成任务!,第2个人移动63个盘子(AB)的过程: 命令第 3 个人将 62 个盘子从 A 移到C 上; 自己将剩余的最底下最大的那 1 个盘子从 A 移到 B 上; 再命令第 3 个人将 62 个盘子从 C 移到 B 上; 完成任务!,第3个人移动62个盘子(AC)的过程: 命令第 4 个人将 61 个盘子从 A 移到B 上; 自己将剩余的最底下最大的那 1 个盘子从 A 移到 C 上; 再命令第 4 个人将 61个盘子从 B 移到 C 上; 完成任务!,层层下放!递归调用!,Answer : 递归的结束条件: 最后 1 个人(第 64 个人)只需要移动 1 个盘子。 注意: 第 1 个人完成任务的前提是:第 2 个人完成了任务 第 2 个人完成任务的前提是:第 3 个人完成了任务 第 63 个人完成任务的前提是:第 64 个人完成了任务 所以: 只有当第 64 个人完成任务后,第 63 个人才能完成任务;只有第 264 个人完成任务后,第 1 个人才能完成任务! 典型的递归问题!,思考:递归的结束条件是什么?,进一步分析: 欲将 A 上 3 个盘子移到 C 上,用递归思想可以分解为以下 3 步: 将 A 上 2 个盘子移到 B 上(借助 C ) 将 A 上 1 个盘子移到 C 上 将 B 上 2 个盘子移到 C 上(借助 A ) (其中,第 2 步可以直接实现。) 第 1 步又可以用递归方法分解为: 将 A 上 1 个盘子从 A 移到 C 将 A 上 1 个盘子从 A 移到 B 将 C 上 1 个盘子从 C 移到 B 第 3 步又可以用递归方法分解为: 将 B 上 1 个盘子从 B 移到 A 将 B 上 1 个盘子从 B 移到 C 将 A 上 1 个盘子从 A 移到 C,综上,便可得到移动 3 个盘子的步骤为 AC AB CB AC BA BC AC,结论,将 n 个盘子从 A 移到 C 可以分解为以下 3 个步骤: 将 A 上 n-1 个盘子借助 C 先移到 B 上; 把 A 上剩下的 1 个盘移到 C 上; 将 n-1 个盘从 B 借助 A 移到 C 上。 上面第 1 步和第 3 步,都是把 n-1 个盘子从 1 个座移到另 1 个座上, 采用的办法是相同的,只是座的名字不同而已。为使之一般化,可以将第 1 步和第 3 步表示为: 将 one 座上 n-1 个盘子移到 two 座(借助 three 座)。 只是在第 1 步和第 3 步中,one、two、three 和 A、B、C 的对应关系不同。 对第 1 步,对应关系是:oneA,twoB,threeC 。 对第 3 步,对应关系是:oneB,twoC,threeA 。,因此,将上面 3 个步骤分为 2 类操作: 将 n-1 个盘子从 1 个座移到另 1 个座上 (当 n1 时); 将最后 1 个盘子从 1 个座上移到另 1 个座上 。 设计程序 分别用 2 个函数实现以上 2 类操作: 设计 hanoi 函数实现第 1 类操作; 函数 hanoi( n, one, two, three ) 将实现把 n 个盘子从 one 座借助 two 座移到 three 座的过程; 设计 move 函数实现第 2 类操作; 函数 move( x, y ) 将实现把 1 个盘子从 x 座移到 y 座的过程。x 和 y 是代表 A、B、C 座之一,根据每次不同情况分别以 A、B、C 代入。,#include using namespace std; int main() void hanoi(int n,char one,char two,char three); int m; printf( “input the number of diskes: “ ); scanf( “%d“, ,void move(char x, char y) printf( “%c%cn“, x, y ); ,/将n个盘从one座借助two座,移到three座 void hanoi(int n,char one, char two,char three) void move(char x,char y);/声明 if(n=1) move(one,three); else hanoi(n-1,one,three,two); move(one,three); hanoi(n-1,two,one,three); ,函数调用 move( x, y ) 表示将 1 个盘子从 x 座移到 y 座的过程。x 和 y 是代表 A、B、C 座之一,根据每次不同情况分别以 A、B、C 代入。,函数调用 hanoi( n-1, two, one, three ) 表示将 n-1 个盘子从 two 座借助 one 座移到 three 座的过程;,Hanoi(3,A,B,C)的执行过程,问题:分析m=3时7个步骤是怎么输出来的,依次输出哪个步骤?,/n=3 void hanoi(n,A,B,C) if(n=1) else hanoi(2,A,C,B); move(A,C); hanoi(2,B,A,C); ,/m=3 int main() hanoi(m,A,B,C); ,/n=2 void hanoi(n,A,C,B) if(n=1) else hanoi(1,A,B,C); move(A,B); hanoi(1,C,A,B); ,/n=1 void hanoi(n,A,B,C) if(n=1) move(A,C); else ,/n=2 void hanoi(n,B,A,C) if(n=1) else hanoi(1,B,C,A); move(B,C); hanoi(1,A,B,C); ,/n=1 void hanoi(n,C,A,B) if(n=1) move(C,B); else ,/n=1 void hanoi(n,B,C,A) if(n=1) move(B,A); else ,/n=1 void hanoi(n,A,B,C) if(n=1) move(A,C); else ,运行情况如下: input the number of disks: 4 The steps of moving 4 disks: AB AC BC AB CA CB AB AC BC BA CA BC AB AC BC,8.1.3 递归存在的问题,递归思想虽然很好理解,特别是对于一些可以用递推式子表示的问题。然而,使用递归的代价是十分巨大的:它会消耗大量的内存! 函数调用时需要用到堆栈,而堆栈的资源是十分有限的。 在例8.3中,使用递归思想求Fibonacci数列的第n项。单独求Fibonacci(20),递归调用Fibonacci( )函数就达到21891次!这一点可以用下面的代码验证。,#include int count = 0; /Fibonacci函数递归调用次数 int Fibonacci( int n ) count+; if( n=0 | n=1 ) return 1; else return ( Fibonacci(n-1) + Fibonacci(n-2) ); void main() Fibonacci(20); printf( “count=%dn“, count ); ,8.2 递归思想在竞赛试题中的应用,例8.6 另一个Fibonacci数列(Fibonacci Again) 例8.7 分形(Fractal),例8.6 另一个Fibonacci数列(Fibonacci Again) 题目来源:ZOJ Monthly, December 2003 题号:ZOJ2060,题目描述: 定义另外一个Fibonacci数列:F(0) = 7,F(1) = 11,F(n) = F(n-1) + F(n-2),(n=2)。,输入描述: 输入文件包含多行,每行为一个整数n,n 1,000,000。,第三种输入方式!,输出描述: 对输入文件中的每个整数n,如果F(n)能被3整除,输出“yes“,否则输出“no“。,样例输出: no no yes no,样例输入: 0 1 2 3,分析: 本章例8.3讲解了用递归思想求Fibonacci数列各项,但在本题中如果直接求出第n项,再判断是否能被3整数,则会超时!因为n的值最大可以取到1,000,000。 我们先用下面的程序输出前30项对3取余的结果:,#include int f(int n) if(n=0) return 1; else if(n=1) return 2; else return ( f(n-1)+f(n-2) )%3; int main( ) for( int i=0; i30; i+ ) printf( “%d “, f(i) ); ,前30项对3取余得到的余数分别为:1 2 0 2 2 1 0 1 1 2 0 2 2 1 0 1 1 2 0 2 2 1 0 1 1 2 0 2 2 1。分析这些余数我们发现该Fibonacci数列各项对3取余得到的余数每8项构成循环:1 2 0 2 2 1 0 1。如果我们把这8个余数存放到一个数组f0,对输入的任意整数n,则f(n)%3 = f0n%8。按照这种方法可以很快判断f(n)是否能被3整除。,#include int f(int n) /求第n项对3取余得到的余数 if(n=0) return 1; else if(n=1) return 2; else return ( f(n-1)+f(n-2) )%3; int main( ) int f08; for( int i=0; i8; i+ ) /把前8项的余数保存下来 f0i = f(i); int n; while( scanf( “%d“, ,例8.7 分形(Fractal) 题目来源:Asia 2004, Shanghai (Mainland China), Preliminary 题号:ZOJ2423,POJ2083,题目描述: 分形是存在“自相似”的一个物体或一种量,从某种技术角度来说,这种“自相似”是全方位的。盒形分形定义如下: 度数为1的分形很简单,为: X 度数为2的分形为: X X X X X 如果用B(n-1)代表度数为n-1的盒形分形,则度数为n的盒形分形可以递归地定义为: B(n-1) B(n-1) B(n-1) B(n-1) B(n-1) 你的任务是输出度数为n的盒形分形。,输入描述: 输入文件包含多个测试数据,每个测试数据占一行,包含一个正整数n,n7。输入文件的最后一行为-1,代表输入结束。,第二种输入方式!,输出描述: 对每个测试数据,用符号“X”输出盒形分形。在每个测试数据对应的输出之后输出一个短划线符号“-”,在每行的末尾不要输出任何多余的空格,否则得到的是“格式错误”的结果。,注意:这道题目在ZOJ和POJ上对输出的要求不一样;在ZOJ上要求在每行的末尾不要输出多余的空格,而在POJ上要求每行的宽度一样,这样某些行末尾有若干个空格。,样例输出: X - X X X X X - X X X X X X X X X X X X X X X X X X X X X X X X X -,样例输入: 1 2 3 -1,分析: 首先注意到度数为n的盒形分形,其大小是3n-13n-1。可以用字符数组来存储盒形分形中各字符。因为n7,而36 = 729,因此可以定义一字符数组Fractal730730来存储度数不超过7的盒形分形。 其次,度数为n的盒形分形可以由以下递推式子表示:,B(n-1) B(n-1) B(n) = B(n-1) B(n-1) B(n-1),B(n-

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论