《刘大有版数据结构》 chapter 8 递归

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

以下三种情况,利用递归求解问题是非常有效的 以下三种情况,利用递归求解问题是非常有效的. 1.问题的定义是递归的,例如数学上常用的阶乘函数、 问题的定义是递归的, 问题的定义是递归的 例如数学上常用的阶乘函数、 幂函数和斐波那契数列. 幂函数和斐波那契数列
例8.1 求解阶乘函数的递归过程 long Factorial(long n) ( ) { if (n = = 0) // 递归终止条件 ) return 1; ; else return n *Factorial(n-1); // 递归调用过程 ( ) }
为了把上述递归过程转化为迭代过程, 为了把上述递归过程转化为迭代过程,我们使用一个堆栈 S,用它来存储递归调用的返回路径.栈S的元素是一个 ,用它来存储递归调用的返回路径. 的元素是一个 四元组( , , , ),意为从i柱的上部移 个圆盘到k柱 ),意为从 柱的上部移n个圆盘到 四元组(n,i,j,k),意为从 柱的上部移 个圆盘到 柱 (j柱作为中间柱). 柱作为中间柱) 算法HI( ) 算法 (m) HI1[建立堆栈 建立堆栈] 建立堆栈 CREATS(S). ( ) HI2[堆栈初始化 堆栈初始化] 堆栈初始化 S(m,1,2,3). , , , ) HI3[利用栈实现递归 利用栈实现递归] 利用栈实现递归 WHILE NOT(StackEmpty(S)) ))DO ( ( )) ((n, , , ) (( ,i,j,k) S. . IF n = 1 THEN MOVE(i,k) (, ) ELSE(S(n-1,j,i,k). ( - ,,, ) S(l,i,j,k) . ,,, ) S(n-1,i,k,j)) ▌ - , , , ))
第八章 递归
8.1 什么是递归 8.2 基本递归过程 8.3 递归过程的实现:堆栈与递归 递归过程的实现: 8.4 递归到非递归的转换 8.5 递归的应用
8.1 什么是递归
定义8.1 如果一个对象部分地包含它自己,或者利用自己定 如果一个对象部分地包含它自己, 定义 义自己的方式来定义或表述,则称这个对象是递归的 对象是递归的; 义自己的方式来定义或表述 , 则称这个 对象是递归的 ; 如果一个过程直接或间接地调用自己, 如果一个过程直接或间接地调用自己 , 则称这个过程是 一个递归过程 递归过程. 一个递归过程 任何一个有意义的递归总是由两部分组成: 任何一个有意义的递归总是由两部分组成 : 递归调用 与递归终止条件. 与递归终止条件.
Fib(n) =
n Fib(n 1) + Fib(n 2)
n = 0或1 n≥2
下图是求Fib(5)时的递归调用树,从而可以计算出算法 ( )时的递归调用树, 下图是求 的复杂度为○ 的复杂度为○(2k).
Fib(5)
Fib(4)
Fib(3)
Fib(3)
Fib(2)
Fib(2)
Fib(1)
该函数无返回值,有四个形参,无局部变量, 该函数无返回值,有四个形参,无局部变量,每个工作记 录包含五个数据(返回语句标号,四个形参) 录包含五个数据(返回语句标号,四个形参). 递归过程 中包含两个递归调用语句,需要设0, , , 四个语句标 中包含两个递归调用语句,需要设 ,1,2,3四个语句标 根据上述变换规则,可以将算法8.7改写成算法 改写成算法8.8的 号,根据上述变换规则,可以将算法 改写成算法 的 非递归过程. 非递归过程 塔非递归过程的C++语言描述 例8.8 Hanoi 塔非递归过程的 语言描述 void hanoi(int n,char x,y,z) ( , , , ) { struct levelstr { int adr,ptrn; , ; char xp,yp,zp; , , ; } currentp; ;
8.4 递归到非递归的转换
递归过程结构清晰,程序易读,正确性容易证明,但是 递归过程结构清晰,程序易读,正确性容易证明, 运行的效率比较低, 运行的效率比较低,无论是时间和空间都比非递归程序更 至少在两种情况下,消除递归是有意义的:( :(1 费.至少在两种情况下,消除递归是有意义的:(1)程序 中频繁调用的部分;( ;(2 程序设计语言不允许递归调用. 中频繁调用的部分;(2)程序设计语言不允许递归调用. 本书介绍一种利用基本语句模拟递归调用的变换方法, 本书介绍一种利用基本语句模拟递归调用的变换方法, 它适用于大多数递归过程(或函数) 它适用于大多数递归过程(或函数). 虽然由此变换得到 的非递归过程结构不够清晰,但是, 的非递归过程结构不够清晰,但是,如果这个递归过程被 证明是正确的,并且保证每一步的变换也是正确的, 证明是正确的,并且保证每一步的变换也是正确的,则所 得非递归过程也是正确的。 得非递归过程也是正确的。 本书介绍了9条递归过程(函数)转换为非递归的通用 本书介绍了9条递归过程(函数) 变换规则。 变换规则。
假定用MOVE(i,j)表示把 柱最靠上的圆盘移到 柱上这 ( , )表示把i柱最靠上的圆盘移到 柱最靠上的圆盘移到j柱上这 假定用 一过程,则由上述递归过程, 一过程, 则由上述递归过程,就很容易给出如下的递归算 法. 例8.5 算法 HR(n,i,j,k) ( ,,, ) // 把原柱 上的 个圆盘移到目标柱 上,圆柱 是中间柱 把原柱i上的 个圆盘移到目标柱k上 圆柱j是中间柱 上的n个圆盘移到目标柱 HR1[递归出口 递归出口] 递归出口 IF n = 1 THEN(MOVE(i,k).RETURN) . ( (, ) ) HR2[递归调用 递归调用] 递归调用 HR(n-l,i,k,j) . ( ,, ,) MOVE(i,k) . (, ) HR(n-l,j,i,k) ▌ ( ,,, )
作为递归过程的另一个例子,我们讨论 塔问题。 作为递归过程的另一个例子,我们讨论Hanoi塔问题。 塔问题 该问题要求把圆柱1 称为原柱)上的n个圆盘移到圆柱3 该问题要求把圆柱1(称为原柱)上的n个圆盘移到圆柱3 目标柱) 并保持原来圆柱1上的次序; (目标柱)上,并保持原来圆柱1上的次序;移动过程中 可使用圆柱2 中间柱),但必须保证在任何时刻, ),但必须保证在任何时刻 可使用圆柱2(中间柱),但必须保证在任何时刻,大圆 盘不能放在小圆盘之上。 盘不能放在小圆盘之上。 Hanoi塔问题可用如下的递归过程来解决: 塔问题可用如下的递归过程来解决: 塔问题可用如下的递归过程来解决 l) 如果n l,即只有一个圆盘, (l) 如果n = l,即只有一个圆盘,则可直接从原柱移到目 标柱上; 标柱上; 个圆盘按上述要求移到中间柱上; (2) 对于 ) 对于n≥2,先把前 -l个圆盘按上述要求移到中间柱上; ,先把前n- 个圆盘按上述要求移到中间柱上 然后把第n个圆盘移到目标柱上; 最后把中间柱上的n-l个 然后把第 个圆盘移到目标柱上 最后把中间柱上的 - 个 个圆盘移到目标柱 圆盘按上述要求移到目标柱上( 圆盘按上述要求移到目标柱上 ( 此时原柱作为中间柱使 用)。
(1) BS( A, i, m1 ), BS( A, i, mid), (3)BS( A, m1 +1, mid), (2) 合并 BS( A, mid +1, m2 ), (4) BS( A, i, j), (7) BS( A, mid +1, j), (6) BS( A, m2 +1, j), (5) 合并 合并
局部变量
返回地址 参 数 …… 局部变量 返回地址 参 数
递归方法虽然在解决某些问题时,是最直观、 递归方法虽然在解决某些问题时, 是最直观 、 最方便的方 但却并不是一种高效的方法, 法,但却并不是一种高效的方法,主要原因在于递归方法 过于频繁的函数调用和参数传递. 在这种情况下, 过于频繁的函数调用和参数传递 在这种情况下 , 若采用 循环或递归算法的非递归实现, 循环或递归算法的非递归实现,将会大大提高算法的执行 效率. 效率 以计算斐波那契数列的递归函数Fib(n)为例, 它的递归 ( )为例, 以计算斐波那契数列的递归函数 定义公式如下: 定义公式如下:
本章把第二章的例2.3算法 作为例 中给出,显然算法 本章把第二章的例 算法BS作为例 中给出 显然算法 算法 作为例8.4中给出 BS就是一个递归过程。该递归算法的子过程的调用过程和 就是一个递归过程。 就是一个递归过程 子过程完成的先后次序如下图所示,可见, 子过程完成的先后次序如下图所示,可见,子过程的产生 到完成正好是这些子过程进栈、出栈的次序。 到完成正好是这些子过程进栈、出栈的次序。
下面举例说明上述变换法则的使用方法. 下面举例说明上述变换法则的使用方法 塔递归过程的C++语言描述 语言描述. 例8.7 Hanoi塔递归过程的 塔递归过程的 语言描述 void hanoi(int n,char x,y,z) ( , , , ) { if (n = = 1) move(x,1,z);ห้องสมุดไป่ตู้) ( , , ) else { hanoi(n-1,x,z,y); ( - , , , ) move(x,n,z); ( , , ) hanoi(n-1,y,x,z); ( - , , , ) } }
利用“递归工作栈”保存信息,每执行一次递归调用, 利用“递归工作栈”保存信息,每执行一次递归调用, 系统就需要建立一个新的工作记录, 系统就需要建立一个新的工作记录,并将其压入递归工作 每退出一层递归, 栈;每退出一层递归,就从递归工作栈中弹出一个工作记 录.栈顶的工作记录必定是当前正在执行的这一层递归调用 栈顶的工作记录必定是当前正在执行的这一层递归调用 的工作记录,所以又称之为“当前活动工作记录” 的工作记录,所以又称之为“当前活动工作记录”
Fib(2)
Fib(1 )
Fib(1) Fib(0)
Fib(1)
Fib(0)
Fib(1) Fib(0)
如果我们采用简单的循环语句计算斐波那契数列的第n项 如果我们采用简单的循环语句计算斐波那契数列的第 项, 则算法的复杂度为 ○(n) . 例8.3 计算斐波那契数列的非递归函数 long CalFib(long n) ( ) { if(n < = 1) ( ) return n; ; else { long f1 = 1,f2 = 0,f = 0; , , ; for( int i = 2;i < = n;i++) ( ; ; ) { f = f1+f2;f2 = f1;f1 = f;} ; ; ; return f; ; } }
8.3 递归过程的实现:堆栈与递归 递归过程的实现:
通过自己设计的栈, 通过自己设计的栈,我们可以用非递归的基于栈的算法代 替递归算法求解这些问题. 替递归算法求解这些问题 对于堆栈,如下的几个操作是必须的: 对于堆栈,如下的几个操作是必须的: (1) CREATS(S)建立空堆栈 ; ( )建立空堆栈S; (2) SA把元素 压入堆栈 ; 把元素A压入堆栈 把元素 压入堆栈S; (3) xS删除栈顶节点,并赋值给 ; 删除栈顶节点, 删除栈顶节点 并赋值给x; (4) StackEmpty(S)若S为空,则返回 ,否则返回 . 为空, ( ) 为空 则返回1,否则返回0
2. 问题所涉及的数据结构是递归的,例如链表就是 问题所涉及的数据结构是递归的, 一种递归的数据结构. 一种递归的数据结构
例8.2 template <class T> void Search(Node <T> *p,T item) ( , ) { if(p = = NULL) ( ) {cerr <<“Can not find the item .” ;return ;} if (p->data = = item) ) Dealwith(p); ( ) else Search(p->next,item); ( , ) }
3. 问 题 的 解 法 满 足 递 归 的 性 质 , 典 型 的 例 子是 Hanoi塔问题 塔问题. 塔问题
8.2 基本递归过程
递归调用分为内部调用与外部调用,调用的方式不同, 递归调用分为内部调用与外部调用,调用的方式不同,调 用结束时返回的方式也不相同. 用结束时返回的方式也不相同. 每次递归过程调用时,必须做地址保存、参数传递等工作 每次递归过程调用时,必须做地址保存、参数传递等工作. 需要保存的信息构成一个工作记录,通常包括如下内容: 需要保存的信息构成一个工作记录,通常包括如下内容: (1)返回地址,即本次递归过程调用语句的后继语句的地 )返回地址, 址; (2)本次调用中与形参结合的实参值,包括函数名、引用 )本次调用中与形参结合的实参值,包括函数名、 参数与数值参数等; 参数与数值参数等; (3) 本次递归调用中的局部变量值 ) 本次递归调用中的局部变量值.
相关文档
最新文档