收藏 分享(赏)

算法课件 第二章 递归.ppt

上传人:dreamzhangning 文档编号:3358036 上传时间:2018-10-18 格式:PPT 页数:74 大小:220.50KB
下载 相关 举报
算法课件 第二章 递归.ppt_第1页
第1页 / 共74页
算法课件 第二章 递归.ppt_第2页
第2页 / 共74页
算法课件 第二章 递归.ppt_第3页
第3页 / 共74页
算法课件 第二章 递归.ppt_第4页
第4页 / 共74页
算法课件 第二章 递归.ppt_第5页
第5页 / 共74页
点击查看更多>>
资源描述

1、第二章 递归,一个算法为完成它的复杂工作,可以应用其他算法完成一部分工作。 算法A应用算法B,算法B又应用算法C,算法C又应用算法D,等等。这样,从算法A出发,形成一个长长的算法应用链。若将每个算法分别用相应的函数实现,上述实现方案就会构成通常所说的函数嵌套调用。 函数嵌套调用时,先被调用的函数后返回。如上述从函数A()出发调用函数B(),函数B()调用函数C(),函数C()又调用函数D(),待函数D()完成计算返回后,C()函数继续计算(可能还要调用其他函数),待函数C()计算完成,返回到函数B(),函数B()计算完成后,才返回到函数A()。,2.1递归的概念,当算法调用链上的某两个算法为同

2、一个算法时,称这种算法实现方式为递归的。通过递归方式完成其功能的算法称为递归算法。许多问题的求解方法具有递归特征,用递归算法描述这种求解算法,能使算法更简洁。计算n的阶乘(n!)算法就是一个很好的例子。因 n! = 1 * 2 * 3 * * n,【例2.1】用循环实现阶乘计算 long fac(int n) long s;int i;for(s = 1L, i = 1; i = n; i+)s *= i;return s; ,把 n! 的定义改写成以下形式 (1) n! = 1 , n 1。 根据这个定义形式用递归描述如下: 【例2.2】用递归实现阶乘计算long fac(int n)if(

3、n = 1) return 1L;return n*fac(n-1);,m = fac(3) 的计算过程可大致叙述如下: 为计算3!,以函数调用fac(3)去调用函数fac();函数fac(n=3)为计算3*2!,用fac(2) 去调用函数fac();函数fac(n = 2)为计算2*1!,用fac(1)去调用函数fac();函数fac(n = 1)计算1!,以结果1返回;返回到发出调用fac(1)处,继续计算得到2!的结果2返回;返回到发出调用fac(2)处,继续计算得到3!的结果6返回。,递归计算n!有一个重要特征,为求n有关的解,化为求n-1的解,求 n-1的解又化为求n-2的解,如此类

4、推。特别地,对于1的解是可立即得到的。这是将大问题解化为小问题解的递推过程。有了1的解以后,接着是一个回溯过程,逐步获得2的解,3的解, 直至n的解。,递归算法的执行过程分递推和回归两个阶段。在递推阶段,把较复杂的问题(规模为n)的求解递推到比原问题简单一些的若干子问题(规模小于n)的求解。例如上例中,求解fac(n),把它递推到求解fac(n-1)。也就是说,为计算fac(n),递推到计算fac(n-1),而计算fac(n-1),又把它递推到计算fac(n-2)。依此类推,直至计算fac(1),能立即得到结果1。在递推阶段,必须要有终止递推的情况,例如在函数fac()中,当n为1情况。,在回

5、归阶段,当获得最简单情况的解后,逐级回归,依次获得稍复杂问题的解。例如得到fac(1)为1后,返回获得fac(2)的结果,得到了fac(n-1)的结果后,返回获得fac(n)的结果。上述递推和回归过程可用图示意。递归次序n! (n-1)! (n-2)! 1!=1返回次序图2.1 递归的递推和回归过程示意图,【例2.3】用递归实现数组元素求和的计算 实现方案有许多,一个比较直观的方法,可把数组元素的累计由当前元素与数组其余元素的和来实现,而对数组其余元素的求和通过递归实现。int rsum(int *a, int n)if (n = 0) return 0; /* 若数组没有元素,则返回0 */

6、* 当前元素与其余元素的和 */return *a + rsum(a+1, n-1);,【例2.4】Hanoi塔问题。 有A、B、C三根针,和一些大小不等中间有孔的金片。初始时,金片全在A针上,其中最大的在下面,从大到小,最小的在最上面。要求将A针上的金片全部移到B针上。限制每次只允许移一张金片,并且不允许将大金片放在小金片的上面,但移动时可以将金片放在C针上。问题要求给出金片的移动过程。,移动A针上的最上面n张金片到B针的算法可递归实现,由以下三个步骤组成:1) 将A针最上面的n-1张金片移至C针;2) 将现在露在A针最上面的第n张金片移至B针;3) 将暂存于C针上的n-1张金片移至B针。

7、上述算法实际上是采用分治法思想来设计的,把上面的n-1张金片的移动与最下面的一张金片的移动分开,从而将n张金片的移动转换成更少张数金片的移动。 将n张金片按小到大编号为1,2,n,并将搬动要求抽象成:将n张金片从f针搬到t针,搬动过程可使用u针。采用以下递归算法求解金片的搬动步骤:,void move(int n, char f, char t, char u)/*将n张金片从f针搬到t针,搬动过程可使用u针*/if (n=1) /* 对于只搬动一张金片情况 */(1) 将金片 n 从 f 针搬到 t 针;else /*按同样算法先搬前 n-1 张金片 */(2) 搬f针的n-1张金片到u针上

8、,中间可使用 t 针;(3) 将金片 n 从 f 针搬到 t 针; /* 搬 u 针上的 n-1 张金片 */(4) 搬u针上的n-1张金片到t针,中间可使用f针;,欲将A针上的3张金片搬到C针上:move(3,A,C,B) 这时,n=3,f=A,t=C,u=B。 因n = 3(1),函数将上述要求分解成以下三个步骤:(1)将f(=A)针上的2张金片搬到u(=B)针上,可以利用t针; (2)将f(=A)针上的3号针片搬到t(=C)针上; (3)将u(=B)针上的2张金片搬到t(=C)针上,可以利用f针;,第(1)步将使用函数调用move(2, A, B, C)实现 该步骤又分解为以下三个步骤(

9、注意,f=A,t=B,u=C):将f(=A)针上的 1 张金片搬到u(=C)针上;将f(=A)针上的 2 号金片搬到t(=B)针上;将u(=C)针上的 1 张金片搬到t(=B)针上。 其中,和又分别通过递归调用move(1, A, C, B) 和 move (1, C, B, A) 实现。 这两个调用因n=1,不再递归调用,分别完成一次金片搬动。 第(3)步也将使用函数调用move(2, B, C, A)实现,这次函数调用又将分解为三个步骤,其中两个步骤再通过以下递归调用来实现:move (1, B, A, C)和 move (1,A,C,B),将1号金片从A针搬到C针;将2号金片从A针搬到B

10、针;将1号金片从C针搬到B针;将3号金片从A针搬到C针;将1号金片从B针搬到A针;将2号金片从B针搬到C针;将1号金片从A针搬到C针。,int count;void astep(int m, char from, char to)printf(“%3d %5d: %c-%cn“, +count, m, from, to);void move(int n, char f, char t, char u)if (n = 1) astep(1, f, t);else move(n-1, f, u, t); astep(n, f, t); move(n-1, u, t, f);,当一个算法直接或间接通

11、过其自身定义时,就称该算法是递归定义的。 算法直接由自身定义情况,称为直接递归。 算法间接地由其自身定义情况,称为间接递归。 通常,以递归形式定义算法与非递归算法比较,算法结构会更紧凑,清晰。但是,递归函数在递归调用过程中,会占用更多的内存空间和需更多的运行时间。另外,在编写递归函数时,必须防止遗漏递归终止条件和防止振荡式的相互递归调用,否则会产生无限递归调用现象。 采用递归描述的算法通常有这样的特征:为求解规模大小为N的问题,设法将它分解成一些规模较小的子问题,然后对这些子问题的解进行综合,从这些小问题的解方便地构造出大问题的解。并且这些规模较小的问题也能采用同样的分解和综合方法,分解成规模

12、更小的子问题,并从这些更小子问题的解综合后,构造出规模稍大问题的解。特别地,当规模大小N = 1或N=0时,能直接得到解。,2.2 递归求解实例【例2.5】计算斐波那契(Fibonacci)数列的第n项fib(n)。 斐波那契(Fibonacci)数列为:0,1,1,2,3,。即fib(0) = 0,fib(1) = 1, fib(n) = fib(n-2) + fib(n-1),(当n 1时)。 把计算斐波那契数列第n项值的函数,写成递归函数有:int fib(int n) if (n = 0) return 0;if (n = 1) return 1;if (n 1) return fib

13、(n-2) + fib(n-1); 图2.2是计算斐波那契数列fib(4)的递归调用树。,图2.2 计算fib(4)的递归调用树,由于递归引起一系列的函数调用,并且可能会有一系列的重复计算,递归算法的执行效率相对较低。如图2.2所示,Fib(2)重复计算2次,Fib(1)重复计算3次等。 所以,当某个递归算法能方便地转换成递推算法时,通常就按递推算法编写程序。例如上例计算斐波那契数列的第n项的函数fib(n)应采用递推算法,即从斐波那契数列的前两项值出发,逐次由前两项计算出下一项,直至计算出要求的第n项。,【例2.6】 找出从自然数1、2、n中任取r个数的所有组合。例如n = 5,r = 3,

14、所有组合为: 5 4 35 4 25 4 15 3 25 3 15 2 14 3 24 3 14 2 13 2 1,递归思想来考虑求组合函数的算法: 设函数为 void comb(int m, int k),即找出从自然数1、2、m中任取k个数的所有组合。当组合的第一个数字选定时,其后的数字是从余下的数字中取k-1个数的组合。 这就将求m个数中取k个数的组合问题,转化成在较小数字范围内取k-1个数的组合问题。,函数引入工作数组a存放求出的组合的数字,约定函数将确定的k个数字组合的第一个数字放在ak-1中,当一个组合的全部元素都求出后,才将a中的一个组合输出。第一个数可以是m、m-1、k。函数将

15、确定的组合的第一个数字放入数组后,有两种可能的选择: (1)还未确定组合的其余元素,继续递归去确定组合的其余元素; (2)已确定了组合的全部元素,输出这个组合。 对于情况(1),还需确定组合的其余元素,记当前确定的数为i,则接下来的工作应是从1、2、i-1 中完成取k-1个数的组合。 这可用递归调用comb(i-1,k-1)实现。,void comb(int m, int k) int i, j;for(i = m; i = k; i-) ak-1 = i;if (k 1) /* 组合中还有其它元素 */comb(i-1, k-1);else /*找到了一个组合,将组合的元素输出 */for(

16、j = r-1; j = 0; j-) printf(“%4d“, aj);printf(“n“); ,【例2.7】 用已知字符串 s 中的字符,生成由其中 n 个字符组成的所有字符排列。 设 n 小于字符串 s 的字符个数,其中 s 中的字符在每个排列中最多出现一次。例如 s=“abc“,n=2, 则所有字符排列有: ba、ca、ab、cb、ac、bc。 设函数用字符数组 w 存贮生成的字符排列。令递归函数为 perm(int n, char *s),其功能是用字符串 s 中的字符,生成由 n 个字符组成的所有排列。其方法是从字符串 s 依次选用其中的每个字符填写到存储字符排列数组w的第 n

17、 个位置(wn-1)中,然后通过递归调用生成由 n-1 个字符组成的排列。当一个字符被选用后,进一步递归调用时可选用的字符中应不再包含该字符。另外,当发现一个字符排列生成后,就不再递归调用,输出生成的字符列。,【算法】 函数 perm(int n, char *s) 的算法 if ( 一个排列已生成 ) printf(“%s“, w); /* 一个排列生成输出 */else /保存本层次可使用的字符串;依次选本层次可用字符循环完成以下工作 将选用字符填入正在生成的字符排列中;形成进一步递归可使用的字符串;递归调用;交换恢复; ,当递归调用生成 0 个字符的排列时,一个字符排列已生成。为保存本层

18、次可使用的字符串,引入一个字符数组,用于存储进一步递归可使用的字符串。因进一步递归可使用的字符总比本层次可使用的字符少一个字符,可把本层次正选用的字符与其字符串中的首字符交换,而进一步递归可使用的字符串就是从第二个字符开始的字符串。,#define N 20char wN;void perm(int n, char *s) int i;if (n 1) printf(“%sn”, w); /*输出生成的排列 */else /* 设定本层字符,调用生成更深层次字符 */for(i = 0; *(s+i); i+) /* 依次选本层次可用字符 */*(w+n-1) = *(s+i); /* 字符填

19、入字符排列中 */*(s+i) = *s; *s = *(w+n-1);perm(n-1, s+1); /* 递归调用*/*s = *(s+i); *(s+i) = *(w+n-1); /* 恢复s */ ,2.3 递归过程和递归工作栈递归函数中的局部变量和参数是局限于当前调用层的。当递推进入“简单问题”层时,原来层次上的参数和局部变量便被隐蔽起来。在一系列“简单问题”层,它们各有自己的参数和局部变量。 递归调用时,要为这次递归调用使用的参数、递归函数的局部变量等,要另外分配存储空间。在逐层向下递归过程中,这样的分配每次都要进行。每次递归调用结束返回时,当前层的参数、局部变量等相应内存被释放,

20、前一层次的参数、局部变量再次变成活跃。 为实现递归,每次递归调用分配的存储空间称为活动记录。活动记录按后进先出的栈结构组织。活动记录中包括的主要内容有实参、局部变量,及本次递归调用的返回地址等。,【例2.8】用递归方式求数表前n个元素中最大值。 计算数表前n个元素中最大值的递归算式定义如下:a0, n = 1; maxNum(a, n)= max(a0, maxNum(a+1, n-1), n1.即若数组只有一个元素,数组的最大值就是数组的唯一元素;若数组的元素有1个以上,则数组的最大值或是数组的首元素,或是数组其余元素中的最大值,由这两个值的较大者作为整个数组的最大值。,int maxNum

21、(int a , int n) int temp;if (n = 1) return a0;temp = maxNum(a+1, n-1);if(a0 temp) return a0;return temp; ,void main() int x=1, 6, 4,m;m = maxNum(x, 3);printf(“%MAX = %dn”, m); 主函数执行m = maxNum(x, 3)时,系统首先为这次调用为参数a分配内存,并使它指向数组的x的首元素x0;为形参n分配内存,并置值为3;为局部变量temp分配内存;记录本次调用的返回位置,返回到执行将调用结果存于变量m的代码。,第一层:a

22、= &x0 n = 3 temp = ? 返回到执行将结果存于变量m的代码 这次函数执行,因n的值为3,执行代码 temp = maxNum(a+1, n-1)。 再次发出调用maxNum(a+1, n-1)。 第二层:a = &x1 n = 2 temp = ? 返回到执行将结果存于第一层局部变量temp的代码 同样,这次调用的执行,因n的值不等于1,执行代码temp = maxNum(a+1, n-1),第三次调用maxNum(),再次产生以下活动记录。 第三层:a = &x2 n = 1 temp = ? 返回到执行将结果存于第二层局部变量temp的代码 这次调用因n为1,函数以a0(即

23、x2)的值4返回,返回前,先释放第三层的活动记录。返回到每二层调用处,将结果4赋给第二层的局部变量temp。继续执行maxNum()函数,这时的a0实际上是x1,其值为6,使这次执行以6为结果返回,并释放第二层的活动记录。返回到第一层,执行将6赋给第一层的局部变量temp。当时的a0(实际上是x0)值为1,使第一层递归调用返回temp值6,同时释放第一层的活动记录。这样,主函数对maxNum()的调用得到的值为6。,递归函数的执行,系统将要用一个栈存放递归过程中发生的活动记录。逐层递归,逐次产生活动记录。逐层递归返回,以相反的顺序逐一释放活动记录。以递归形式定义算法与非递归算法比较,算法结构会

24、更紧凑,清晰。但是,递归函数在递归调用过程中,会占用更多的内存空间和需更多的运行时间。另外,在编写递归函数时,必须防止遗漏递归终止条件和防止振荡式的相互递归调用,否则会产生无限递归调用现象。,2.4 递归算法的非递归实现,用栈将递归计算改为非递归计算是栈的重要应用之一。 若直接用栈模拟递归调用的执行过程,就可将递归算法机械地改写成非递归算法。但这样做,只是将系统的递归工作栈改为应用程序的局部栈,而相应的非递归算法还要增加关于栈处理的细节。在用栈将递归算法改写成非递归算法时,通常会考虑问题的特定情况,如在栈中增加一些必要的信息,使非递归算法更迎合问题的要求。,【例2.9】迷宫问题。 数据结构 令

25、m行p列的迷宫用二维数组int mazem+2p+2表示,迷宫数据的意义是mazeij 为1表示该位置是墙壁,不可通行; mazeij 为0表示该位置是通路,可以通行。 为了便于程序处理,除表示迷宫入口maze10和出口mazem p+1的数组元素的值为0之外,数组的第0行和第 m+1 行、第0列和第 p+1 列是迷宫的围墙,这些元素的值都为1。,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 入口 0, 0, 1, 0, 0, 0, 1, 1, 0, 1,1, 1, 0, 0, 0, 1, 1, 0, 1, 1,1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0

26、, 1, 0, 0, 0, 1, 0, 0, 0, 出口1, 1, 1, 1, 1, 1, 1, 1, 1, 1,设从某个当前位置出发可以往八个方向前进一步,表示前进方向的方法有许多,一种比较简便的方法是给八个方向编号,并沿某个方向前进一步采用对当前位置坐标的相对偏移来表示,如下图3所示:NW(-1,-1)4 N(-1,0)3 NE(-1,+1)2W(0,-1)5 当前位置 E(0,+1)1SW(+1,-1)6 S(+1,0)7 SE(+1,+1)0 前进方向(行位置偏移,列位置偏移)编号 以上信息可用以下固定数组表示. struct int dx; / dx 是x方向的偏移int dy; /

27、 dy 是y方向的偏移 offsets8 = 1, 1,0, 1,-1, 1,-1, 0,-1, -1, 0, -1,1, -1,1, 0;,在迷宫求解中,若一个位置已被考虑过,则这个位置不应在以后的探索中被重复考虑,为此,程序引入标志矩阵int markm+2p+2,其含义是:markij 为1表示该位置曾走过; markij 为0表示该位置尚未走过; 利用以上数据结构,以下考虑求解算法。首先考虑用递归方法求解。 算法先引入存储解的行走轨迹的数组: struct int x;int y; sm*p:,递归算法应设以下参数: m:迷宫有m+1行 p:迷宫有p+1列 x:当前位置的行 y:当前位

28、置的列 k:递归的层次,用于存储找到的解另外,算法应返回一个是否找到解的信息。若当前位置不能找到解,算法返回0值,若算法找到了解,返回一个解的行走步数,设行走的轨迹存与数组s 中。,【算法】 递归方法求解迷宫问题 int serachMaze(int m,int p,int x,int y,int k) /*迷宫输入,及已走过标志的初值由主函数预置*/保存当前位置; 置当前位置已走过;if(当前位置是出口位置) 返回k; /*找到解*/设置从0号方向开始; 目前还未找到解标志;while (还未找到解,且还未试尽所有方向) 由当前位置和移动方向确定移动后的新位置;if(新位置在迷宫内,且该位置

29、是通路和还未走过的位置)利用新位置递归找解;准备探索下一个方向; 回答找解结果; ,int searchMaze(int m, int p, int x, int y, int k) int d, found, nextx, nexty; sk.x = x; sk.y = y; markxy = 1; /*保存当前位置;并置已走过;if(x = m /*回答找解结果*/ ,在非递归解中,用栈来存储试探过程中所走过的路径和方向。栈的数据元素用如下三元组表示:typedef struct items /当前位置(x,y)和试探方向int x, y; int dir;StackEle; 【算法】求解

30、迷宫问题的非递归算法, 初始化栈和mark数组(入口置已走过,其余位置都未走过);入口进栈(1,0,1);/* 从入口由西向东进入*/while(栈非空) 退栈,栈顶元素作为当前位置和当前探索方向:while(还有移动) 由当前位置和移动方向确定新位置;if(新位置在迷宫内,且该位置是通路和还未走过的位置) dir = 下一次将要移动的方向;/*为确定新的可能方向*/当前位置与新的方向(i,j,dir) 进栈;if(新位置是出口) /*搜索成功*/输出栈中元素;输出出口位置;返回找到解;以新位置作为当前位置继续搜索;d = 0; /* 从东南方开始搜索*/ 设当前位置为已走过; else 搜索

31、下一个方向;/*d+*/ 输出找不到解的报告; ,int markM+2P+2; void mazePath(int m, int p) int i, j, nexti, nextj, k, d, sp = 0;StackEle stack200, temp;for(i = 0; i 0) temp = stack-sp;i = temp.x; j = temp.y; d = temp.dir;while(d = 0 /当前位置与新的方向进栈;,if(nexti = m ,【例2.10】数值表达达式计算。 编制一个能完成加(+)、减(-)、乘(*)、除()四则运算的数值表达式的计算程序。设数值

32、表达式的书写规则与平常习惯一致。数值表达式按以下句法分四级定义::+-:/:():. 表达式由项接上0个或多个连续加减项组成。项由因子接上0个或多个连续乘除因子组成。因子由实数或带括号的表达式组成。实数由整数部分、小数点和小数部分组成,整数部分和小数部分都是数字序列。因子有两种可能形式,或是实数,或是带园括号的表达式; 对若干因子的连续乘除运算构成项; 由若干项的连续加减运算构成表达式。,数值表达式的基本词法单位有以下几种: 实数、四则运算符、园括号、换行符、文件结束、其它的非法符号。 数值表达式的计算程序可以由以下几个函数组成: 分析单词函数getToken()得到下一个基本的词法单位的内部

33、标记。如单词是实数,函数getToken()将返回数值运算分量的内部标记;如单词是运算符,或是其它字符,返回对应字符的内部标记。 函数getToken()首先要做的工作是掠过可能有的空白字符,接着是要判当前非空白字符是否是数字符,如不是数字符,查得该字符的内部标记返回;如是数字符,继续输入数值的全部字符和将构成实数的字符列翻译成机内实数,并返回数的内部标记。,输入字符函数getch()和返还字符函数ungetch()。 注意这样一个事实,为输入一个实数的全部字符,函数必须直至输入不能构成实数的字符时才能结束。这样,就会多输入一个字符,这个字符可能是运算符等有意义的别的字符,函数应把多输入的字符

34、保存于某个地方。函数ungetch()被调用时,将参数中的字符保存于某个数组队列中。 函数getch()被调用时,首先看数组队列中是否还有字符。如有,就从队列中取出第一个字符,作为这次要输入的字符;如队列中没有临时保存的字符,才用标准函数输入一个字符。对于本题,返还的字符只有一个字符,不会连续返还多个字符。但用队列存储返还字符具有一般性,可以用于各种场合。,#include #include #include #define Epsilon 1.0e-5#define NUM 1#define ADD 2#define SUB 3#define MUL 4#define DIV 5#defin

35、e NL 6#define LP 7#define RP 8#define END -1#define ERR 0,int getToken(int *, double *); int getch(void);void ungetch(int);void initBuf(void);double expr(void);double term(void);double factor(void);void main() double result; char ans;while (1)initBuf();printf(“Enter numeric expression!n“);result = e

36、xpr();printf(“The expressions result is %.6fn“, result);printf(“Continue?(y/n)n“);scanf(“ %c“, ,int getToken(int *chp, double *dp) int k, c; double num, rad;while (c = getch() = | c = t) ;if (c 9) ,/*输入字符函数和返还输入字符函数*/#define BUFSIZE 10static int bufBUFSIZE;static int bufp, buft;int getch() int c;if

37、(bufp = buft) return getchar();c = bufbufp; bufp = (bufp + 1) % BUFSIZE;return c;void ungetch(int c) /*返还字符函数*/ int t = (buft+1) % BUFSIZE;if (t = bufp) printf(“ungetchs buffer is FULL!n“);else bufbuft = c; buft = t; void initBuf(void) bufp = buft = 0; ,/*表达式计算函数调用项计算函数完成连续的加减计算*/double expr(void) d

38、ouble ev, tv; int token, ch;ev = term();while (token = getToken(,/* 项计算函数调用因子计算函数完成连续的乘除计算 */ double term() double tv, fv, non; int token, ch;tv = factor();while (token = getToken( ,/* 因子计算函数或获得一个实数值,或递归调用表达式计算函数获得因子的值 */ double factor(void) int token, ch; double res, non;token = getToken( ,非递归方法求数值

39、表达式 程序引入两个栈,运算分量栈和运算符栈。 表达式求值时,自左至右顺序扫描数字表达式字符行。 当遇到数时,将表示数的字符列转换成计算机的内部表示形式,并将它存入运算分量栈; 遇运算符(包括括号)时,用它的优先级(栈外级)和运算符栈的栈顶运算符的优先级(栈内级)进行比较。1)栈外运算符的优先级高,则栈外运算符进运算符栈; 2)栈外运算符的优先级低,则运算符栈的栈顶运算符出栈,相应的运算分量也出栈,并按出栈的运算符作计算,将计算结果存入运算分量栈。直到运算符栈的栈顶运算符的优先级较栈外运算符的优先级低为止,再将栈外运算符进运算符栈。 运算符栈内外优先级作恰当定义,实现表达式自动求值。,给每个运

40、算符引入栈内优先级和栈外优先级,能适应各种运算符的特别要求。如有运算符要求自右向左运算,则它的栈外优先级可比它的栈内优先级高;若运算符要求自左向右运算,则它的栈外优先级可比它的栈内优先级低。 对于本题,如左括号的栈外优先级最高,让它在栈外时能进栈,而左括号的栈内优先级又是最低,让栈外的运算符能进栈。 右括号的栈外优先级与左括号的栈内优先级相等。这就方便地实现了括号内的子表达式优先计算的要求。引入运算符的栈内优先级和栈外优先级,可以简化表达式非递归计算的算法。,【算法】表达式非递归求值 设置初值,包括预先让一个特殊字符进运算符栈;while (数字表达式分析还未结束) 读入一个单词;if (读入

41、非法单词) 结束分析循环;if (当前单词是数) 运算分量进运算分量栈;else if(当前运算符的栈外优先级高于栈顶运算符的优先级) 栈外运算符进栈else while (运算符栈非空,并且当前运算符的栈外优先级低于栈顶运算符的优先级) 运算符栈退栈;if (退栈的运算符是乘幂运算符或四则运算符) 运算分量退栈;运算分量退栈;运算结果进栈; ,if (数值表达式字符行结束) 退出表达式分析循环else if (前单词不是闭括号)栈外运算符进栈; if (表达式分析正常结束) 输出表达式的值 还要考虑数据结构和一些对表达式错误情况的处理。程序将运算分量栈和运算符栈用数组实现。 程序还考虑括号应

42、配对出现,引入括号计数器n;另外运算分量的个数应保持比运算符多一个的要求,为此引入计算器m。 另约定一行一个数字表达式,回车符作为一个表达式的结束符。,#include #include #include #define MAXN 200 #define Epsilon 1e-7 struct char op; int code; /*符号及其内部代码*/ opchTbl = *, 1, /, 2, +, 3, -, 4, (, 5,), 6, n, 7, , -1; int osp = /* *, /, +, -, (, ), n 的栈外优先级 */4, 4, 2, 2, 8, 1, 0,

43、isp = 5, 5, 3, 3, 1, 1, 0; /* *, /, +, -, ( 的栈内优先级 */ int opstackMAXN, *optop; double numMAXN; double *numtop; int error; void synError() printf(“表达式句法错!n“); error = 1; ,double eval(char tag, double left, double right) switch(tag) case 3 : return left + right; /加case 4 : return left - right;/减case 1

44、 : return left * right; /乘case 2 : if (fabs(right) = Epsilon) printf(“除 0 出错!n“);exit(1); return left/right; / 除 return 0; /数的内部代码为0 ,int c = ; #define RADIX 10 int getToken(double *nump, char *op) double dradix, num; int i;while (c = | c = t) c = getchar(); /*掠过空白符*/if(!(c = 0 ,if (c = .) dradix =

45、1.0 / RADIX; c = getchar();while (c = 0 /*预先让左括号进栈*/,while (error = 0) if (type = getToken( /*栈外运算符进栈 */else ,while (optop != opstack /*栈外运算符进栈*/ ,if (error = 0) printf(“表达式的值为%.6fn “, num0); else while (c != n) c = getchar();printf(“继续吗? “);scanf(“ %c%*c“, ,对于一些特别的递归形式,可用特别手段将递归算法改写成非递归算法。常见的递归算法结构

46、形式主要分不要求递归的特殊情况,和要求递归的一般情况。对于递归情况,也只包含对自身的一次递归调用。这种递归结构称为线性递归结构,可以机械地用循环将线性递归改为非递归。线性递归的算法有以下所示的一般形式:p(int n) S(n);if (E(n) p(n-1); /* 递归调用 */T(n);elseU(n);,线性递归能机械地被转换成一个功能等价的非递归:p(int n) int k = n+1;do k-;S(k); while E(k);U(k);while (kn) k+; T(k);,另一类递归结构可用递推来实现。大问题的解有规律地由若干较为简单的小问题的解实现,如下面的例子。 【例

47、2.11】勒让得多项式计算。 勒让得多项式的递归定义为: 1, n=0; p(n,x) = x, n=1; (2n-1)xp(n-1,x)-(n-1)p(n-2,x)/n, n1。 按定义,用递归函数实现:double p(int n, double x) if (n=0) return 1.0;if (n=1) return x;return (2.0*n-1.0)*x*p(n-1,x)-(n-1.0)*p(n-2,x)/n;,从低幂次的勒让得多项式到高幂次的勒让得多项式的递推解法:double p(int n, double x) double first, second, third;int count;if (n = 0) return 1.0;if (n = 1) return x;first = 1.0; second = x;for(count = 2; count = n; count+) third = (2.0*count-1.0)*x*second (count-1.0)*first)/count;first = second; second = third;return third;,

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 大学课件

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报