6.1递归的概念 若一个算法直接的或间接的调用自己本身,则称这个算法是递归算法。 存在算法调用自己的情况: (1)问题的定义是递推的

Slides:



Advertisements
Similar presentations
因数与倍数 2 、 5 的倍数的特征
Advertisements

2.8 函数的微分 1 微分的定义 2 微分的几何意义 3 微分公式与微分运算法则 4 微分在近似计算中的应用.
圆的一般方程 (x-a)2 +(y-b)2=r2 x2+y2+Dx+Ey+F=0 Ax2+Bxy+Cy2+Dx+Ey+ F=0.
计算学科的基本问题 本章首先介绍一个对问题进行抽象的典型实例——哥尼斯堡七桥问题。然后,通过“梵天塔”问题和“停机问题”分别介绍学科中的可计算问题和不可计算问题。从“梵天塔”问题再引出算法复杂性中的难解性问题、P类问题和NP类问题,证比求易算法,P=NP是否成立的问题。
《高等数学》(理学) 常数项级数的概念 袁安锋
小学生游戏.
Oracle数据库 Oracle 子程序.
第5章 定积分及其应用 基本要求 5.1 定积分的概念与性质 5.2 微积分基本公式 5.3 定积分的换元积分法与分部积分法
定积分习题课.
§5 微分及其应用 一、微分的概念 实例:正方形金属薄片受热后面积的改变量..
§5 微分及其应用 一、微分的概念 实例:正方形金属薄片受热后面积的改变量..
C++中的声音处理 在传统Turbo C环境中,如果想用C语言控制电脑发声,可以用Sound函数。在VC6.6环境中如果想控制电脑发声则采用Beep函数。原型为: Beep(频率,持续时间) , 单位毫秒 暂停程序执行使用Sleep函数 Sleep(持续时间), 单位毫秒 引用这两个函数时,必须包含头文件
在PHP和MYSQL中实现完美的中文显示
第5章 递归 5.1 什么是递归 5.2 递归算法的设计 本章小结.
函數(一) 自訂函數、遞迴函數 綠園.
Chap 10 函数与程序结构 10.1 函数的组织 10.2 递归函数 10.3 宏定义 10.4 编译预处理.
强连通分量 无向图 1、任意两顶点连通称该图为连通图 2、否则将其中的极大连通子图称为连通分量 A D C B E 有向图
走进编程 程序的顺序结构(二).
辅导课程六.
元素替换法 ——行列式按行(列)展开(推论)
第2讲 绪论(二).
第一单元 初识C程序与C程序开发平台搭建 ---观其大略
以ISI平台为例,为您演示一下如何在Endnote文献中查看该文献的References
第4章 非线性规划 一维搜索方法 2011年11月.
§2 求导法则 2.1 求导数的四则运算法则 下面分三部分加以证明, 并同时给出相应的推论和例题 .
Online job scheduling in Distributed Machine Learning Clusters
What have we learned?.
本节内容 模拟线程切换 视频提供:昆山滴水信息技术有限公司 官网地址: 论坛地址: QQ交流 :
动态规划(Dynamic Programming)
SOA – Experiment 2: Query Classification Web Service
第4章 非线性规划 4.5 约束最优化方法 2019/4/6 山东大学 软件学院.
第一章 函数与极限.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
数列.
专题作业.
第五章 递归与广义表 递归的概念 递归过程与递归工作栈 递归与回溯 广义表.
C语言程序设计 主讲教师:陆幼利.
简单介绍 用C++实现简单的模板数据结构 ArrayList(数组, 类似std::vector)
顺序表的删除.
线性规 Linear Programming
线 性 代 数 厦门大学线性代数教学组 2019年4月24日6时8分 / 45.
11.1 递归的定义 11.2 常见递归问题 11.3 递归的实现 11.4 递归转化为非递归的一般过程 11.5 递归的时间和空间复杂度
离散数学─归纳与递归 南京大学计算机科学与技术系
第6章 递 归 6.1 递归的概念 6.2 递归算法的设计 6.3 递归过程和递归工作栈 6.4 递归算法的效率分析
姚金宇 MIT SCHEME 使用说明 姚金宇
Chap 10 函数与程序结构 10.1 函数的组织 10.2 递归函数.
Lightweight Data-flow Analysis for Execution-driven Constraint Solving
1、递归的概念 2、递归函数的设计 3、递归过程与递归工作栈 4、例 5、递归过程的非递归化
第4章 Excel电子表格制作软件 4.4 函数(一).
資料結構與C++程式設計進階 遞迴(Recursion) 講師:林業峻 CSIE, NTU 6/ 17, 2010.
用穷举法设计程序 南京师范大学 教育技术系 倪佳慧
§6.7 子空间的直和 一、直和的定义 二、直和的判定 三、多个子空间的直和.
3.16 枚举算法及其程序实现 ——数组的作用.
第二章 类型、对象、运算符和表达式.
本节内容 结构体 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
第4课时 绝对值.
多层循环 Private Sub Command1_Click() Dim i As Integer, j As Integer
学习任务三 偏导数 结合一元函数的导数学习二元函数的偏导数是非常有用的. 要求了解二元函数的偏导数的定义, 掌握二元函数偏导数的计算.
2019/5/20 第三节 高阶导数 1.
第二节 函数的极限 一、函数极限的定义 二、函数极限的性质 三、小结 思考题.
实验目的:掌握数据的顺序存储结构及它们在计算机中的操作。 实验内容:
定义5 把矩阵 A 的行换成同序数的列得到的矩阵,
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
线性规划 Linear Programming
插入排序的正确性证明 以及各种改进方法.
创建、启动和关闭Activity 本讲大纲: 1、创建Activity 2、配置Activity 3、启动和关闭Activity
使用Fragment 本讲大纲: 1、创建Fragment 2、在Activity中添加Fragment
§4.5 最大公因式的矩阵求法( Ⅱ ).
第二次课后作业答案 函数式编程和逻辑式编程
§2 自由代数 定义19.7:设X是集合,G是一个T-代数,为X到G的函数,若对每个T-代数A和X到A的函数,都存在唯一的G到A的同态映射,使得=,则称G(更严格的说是(G,))是生成集X上的自由T-代数。X中的元素称为生成元。 A变, 变 变, 也变 对给定的 和A,是唯一的.
Presentation transcript:

6.1递归的概念 若一个算法直接的或间接的调用自己本身,则称这个算法是递归算法。 存在算法调用自己的情况: (1)问题的定义是递推的 阶乘函数的常见定义是:

也可定义为: 写成函数形式,则为: 这种函数定义的方法是用阶乘函数自己本身定义了阶乘函数,称公式(6 – 3)是阶乘函数的递推定义式。

(2)问题的解法存在自调用 一个典型的例子是在有序数组中查找一个数据元素是否存在的折半查找算法。

设计:按照公式6-3计算阶乘函数的递归算法如下:  6.2递归算法的执行过程 例6-1 给出按照公式6-3计算阶乘函数的递归算法,并给出n = 3时递归算法的执行过程。 设计:按照公式6-3计算阶乘函数的递归算法如下:

long int Fact(int n) { int x; long int y; if(n < 0) //n < 0时阶乘无定义 { printf(“参数错!”); return -1; }  if(n == 0) return 1; else { y = Fact(n - 1); //递归调用 return n * y; }

设计主函数如下 void main(void) { long int fn;   fn = Fact(3); }  主函数用实参n= 3调用了递归算法Fact(3),而Fact(3)要通过调用Fact(2)、Fact(2)要通过调用Fact(1)、Fact(1)要通过调用Fact(0)来得出计算结果。Fact(3)的递归调用过程如图6-2所示。

图6-2 Fact(3)的递归调用执行过程

 例6-2 给出在有序数组a中查找数据元素x是否存在的递归算法,并给出如图6-1所示实际数据的递归算法的执行过程。递归算法如下:

int BSearch(int a[], int x, int low, int high) { int mid; if(low > high) return -1;    //查找不成功  mid = (low + high) / 2; if(x == a[mid]) return mid; //查找成功 else if(x < a[mid]) return BSearch(a, x, low, mid-1); //在下半区查找 else return BSearch(a, x, mid+1, high); //在上半区查找 }

测试主函数设计如下: # include <stdio.h>  main(void) { int a[] = {1, 3, 4, 5, 17, 18, 31, 33}; int x = 17; int bn;  bn = BSearch(a, x, 0,7); if(bn == -1) printf("x不在数组a中"); else printf("x在数组a的下标%d中", bn); }

  BSearch(a, x, 0,7)的递归调用过程如图6-3所示,其中,实箭头表示函数调用,虚箭头表示函数的返回值。

6.3递归算法的设计方法 递归算法既是一种有效的算法设计方法,也是一种有效的分析问题的方法。   递归算法既是一种有效的算法设计方法,也是一种有效的分析问题的方法。 递归算法求解问题的基本思想是:对于一个较为复杂的问题,把原问题分解成若干个相对简单且类同的子问题,这样,原问题就可递推得到解。

  适宜于用递归算法求解的问题的充分必要条件是: (1)问题具有某种可借用的类同自身的子问题描述的性质; (2)某一有限步的子问题(也称作本原问题)有直接的解存在。 当一个问题存在上述两个基本要素时,该问题的递归算法的设计方法是: (1)把对原问题的求解设计成包含有对子问题求解的形式。 (2)设计递归出口。

 例6-3 设计模拟汉诺塔问题求解过程的算法。汉诺塔问题的 描述是:设有3根标号为A,B,C的柱子,在A柱上放着n个盘子, 每一个都比下面的略小一点,要求把A柱上的盘子全部移到C柱 上,移动的规则是: (1)一次只能移动一个盘子; (2)移动过程中大盘子不能放在小盘子上面; (3)在移动过程中盘子可以放在A,B,C的任意一个柱子 上。  

问题分析: 可以用递归方法求解n个盘子的汉诺塔问题。 基本思想: 1个盘子的汉诺塔问题可直接移动。n个盘子的汉诺塔问题可递归表示为,首先把上边的n-1个盘子从A柱移到B柱,然后把最下边的一个盘子从A柱移到C柱,最后把移到B柱的n-1个盘子再移到C柱。4个盘子汉诺塔问题的递归求解示意图如图6-4所示。

图6-4 汉诺塔问题的递归求解示意图

 算法设计:首先,盘子的个数n是必须的一个输入参数,对n个盘子,我们可从上至下依次编号为1,2,…,n;其次,输入参数还需有3个柱子的代号,我们令3个柱子的参数名分别为fromPeg,auxPeg和toPeg;最后,汉诺塔问题的求解是一个处理过程,因此算法的输出是n个盘子从柱子fromPeg借助柱子auxPeg移动到柱子toPeg的移动步骤,我们设计每一步的移动为屏幕显示如下形式的信息: Move Disk i from Peg X to Peg Y 这样,汉诺塔问题的递归算法可设计如下:

void towers(int n, char fromPeg, char toPeg, char auxPeg) { if(n==1) //递归出口 { printf("%s%c%s%c\n", "move disk 1 from peg ", fromPeg, " to peg ", toPeg); return; }   //把n-1个圆盘从fromPeg借助toPeg移至auxPeg towers(n-1,fromPeg,auxPeg,toPeg);  //把圆盘n由fromPeg直接移至toPeg printf("%s%d%s%c%s%c\n", "move disk ", n, " from peg ", fromPeg, " to peg ", toPeg);  //把n-1个圆盘从auxPeg借助fromPeg移至toPeg towers(n-1,auxPeg,toPeg,fromPeg);

测试主函数如下: #include <stdio.h>  void main(void) { Towers(4, 'A', 'C', 'B'); } 程序运行的输出信息如下:

Move Disk 1 from Peg A to Peg B Move Disk 2 from Peg A to Peg C Move Disk 1 from Peg B to Peg C Move Disk 3 from Peg A to Peg B Move Disk 1 from Peg C to Peg A Move Disk 2 from Peg C to Peg B Move Disk 4 from Peg A to Peg C Move Disk 2 from Peg B to Peg A Move Disk 3 from Peg B to Peg C

  总结如下:递归算法的执行过程是不断地自调用,直到到达递归出口才结束自调用过程;到达递归出口后,递归算法开始按最后调用的过程最先返回的次序返回;返回到最外层的调用语句时递归算法执行过程结束。

6.4递归过程和运行时栈 对于非递归函数,调用函数在调用被调用函数前,系统要保存以下两类信息: (1)调用函数的返回地址;  对于非递归函数,调用函数在调用被调用函数前,系统要保存以下两类信息: (1)调用函数的返回地址; (2)调用函数的局部变量值。  当执行完被调用函数,返回调用函数前,系统首先要恢复调用函数的局部变量值,然后返回调用函数的返回地址。

  递归函数被调用时,系统要作的工作和非递归函数被调用时系统要作的工作在形式上类同,但保存信息的方法不同。   递归函数被调用时,系统需要一个运行时栈.系统的运行时栈也要保存上述两类信息。每一层递归调用所需保存的信息构成运行时栈的一个工作记录,在每进入下一层递归调用时,系统就建立一个新的工作记录,并把这个工作记录进栈成为运行时栈新的栈顶;每返回一层递归调用,就退栈一个工作记录。因为栈顶的工作记录必定是当前正在运行的递归函数的工作记录,所以栈顶的工作记录也称为活动记录。

6.5递归算法的效率分析 斐波那契数列Fib(n)的递推定义是: 求第n项斐波那契数列的递归函数如下: long Fib(int n) { if(n == 0 || n == 1) return n; //递归出口 else return Fib(n-1) + Fib(n-2); //递归调用 }

图6-6 Fib(5)的递归调用树 用归纳法可以证明求Fib(n)的递归调用次数等于2n-1;计算斐波那契数列的递归函数Fib(n)的时间复杂度为O(2n)。计算斐波那契数列Fib(n)问题,我们也可根据公式写出循环方式求解的函数如下:

long Fib2(int n) { long int oneBack, twoBack, current; int i;  if(n == 0 || n == 1) return n; else { oneBack = 1; twoBack = 0; for(i = 2; i <= n; i++) { current = oneBack + twoBack; twoBack = oneBack; oneBack = current; } return current;

上述循环方式的计算斐波那契数列的函数Fib2(n)的时间复杂度为O(n)。对比循环结构的Fib2(n)和递归结构的Fib(n)可发现,循环结构的Fib2(n)算法在计算第n项的斐波那契数列时保存了当前已经计算得到的第n-1项和第n-2项的斐波那契数列,因此其时间复杂度为O(n);而递归结构的Fib(n)算法在计算第n项的斐波那契数列时,必须首先计算第n-1项和第n-2项的斐波那契数列,而某次递归计算得出的斐波那契数列,如Fib(n-1)、Fib(n-2)等无法保存,下一次要用到时还需要重新递归计算,因此其时间复杂度为O(2n) 。

6.6递归算法到非递归算法的转换 有些问题需要用低级程序设计语言来实现,而低级程序设计语言(如汇编语言)一般不支持递归,此时需要采用问题的非递归结构算法。一般来说,存在如下两种情况的递归算法。 (1)存在不借助堆栈的循环结构的非递归算法,如阶乘计算问题、斐波那契数列的计算问题、折半查找问题等。这种情况,可以直接选用循环结构的算法。

(2)存在借助堆栈的循环结构的非递归算法,所有递归算法都可以借助堆栈转换成循环结构的非递归算法,如下边例6-4设计的汉诺塔问题的借助堆栈的循环结构的非递归算法。此时可以把递归算法转换成相应的非递归算法,有两种转换方法:一种方法是借助堆栈,用非递归算法形式化模拟递归算法的执行过程;另一种方法是根据要求解问题的特点,设计借助堆栈的循环结构算法。这两种方法都需要使用堆栈,这是因为堆栈的后进先出特点正好和递归函数的运行特点相吻合。下面讨论的例6-4是第二种情况下的第一种转换方法的例子

6.7设计举例 6.7.1 一般递归算法设计举例 例6-5 设计一个输出如下形式数值的递归算法。 n n n ... n ...... 3 3 3 2 2 1

问题分析:该问题可以看成由两部分组成:一部分是输出一行值为n的数值;另一部分是原问题的子问题,其参数为n-1。当参数减到0时不再输出任何数据值,因此递归的出口是当参数n≤0时空语句返回。 算法设计:递归算法设计如下: void Display(int n) { int i;  for(i = 1; i <= n; i++) cout<<setw(s)<<n; cout<<endl;   if(n > 0) Display(n - 1); //递归  //n<=0为递归出口,递归出口为空语句 }

例6-6 设计求解委员会问题的算法。委员会问题是:从一个有n个人的团体中抽出k (k≤n)个人组成一个委员会,计算共有多少种构成方法。 问题分析:从n个人中抽出k(k≤n)个人的问题是一个组合问题。把n个人固定位置后,从n个人中抽出k个人的问题可分解为两部分之和:第一部分是第一个人包括在k个人中,第二部分是第一个人不包括在k个人中。对于第一部分,则问题简化为从n-1个人中抽出k-1个人的问题;对于第二部分,则问题简化为从n-1个人中抽出k个人的问题。图6-7给出了n=5, k=2时问题的分解示意图。

图6-7 委员会问题分解示意图

当n=k或k=0时,该问题可直接求解,数值均为1,这是算法的递归出口。因此,委员会问题的递推定义式为:

int Comm(int n, int k) { if(n < 1 || k < 0 || k > n) return 0;   if(k == 0) return 1; if(n == k) return 1; return Comm(n-1, k-1) + Comm(n-1, k); }

例6-7 求两个正整数n和m最大公约数的递推定义式为: 要求: (1)编写求解该问题的递归算法; (2)分析当调用语句为Gcd(30, 4)时算法的执行过程和执行结果; (3)分析当调用语句为Gcd(97, 5)时算法的执行过程和执行结果; (4)编写求解该问题的循环结构算法。

解:(1)递归算法如下: int Gcd(int n, int m) { if(n < 0 || m < 0) exit(0); if(m == 0) return n; else if(m > n) return Gcd(m, n); else return Gcd(m, n % m); }

(2)调用语句为Gcd(30, 4)时,因m<n,递归调用Gcd(4, 2);因m<n,递归调用Gcd(2, 0);因m=0,到达递归出口,函数最终返回值为n=2,即30和4的最大公约数为2。 (3)调用语句为Gcd(5,97)时,因m>n,递归调用Gcd(97, 5);因m<n,递归调用Gcd(5, 2);因m<n,递归调用Gcd(2, 1);因m<n,递归调用Gcd(1, 0);因m=0,到达递归出口,函数最终返回值为n=1,即5和97的最大公约数为1。

6.7.2 回溯法及其设计举例 回溯法是递归算法的一种特殊形式,回溯法的基本思想是:对一个包括有很多结点,每个结点有若干个搜索分支的问题,把原问题分解为对若干个子问题求解的算法。当搜索到某个结点、发现无法再继续搜索下去时,就让搜索过程回溯退到该结点的前一结点,继续搜索这个结点的其他尚未搜索过的分支;如果发现这个结点也无法再继续搜索下去时,就让搜索过程回溯(即退回)到这个结点的前一结点继续这样的搜索过程;这样的搜索过程一直进行到搜索到问题的解或搜索完了全部可搜索分支没有解存在为止。

作业 习题6-6,6-8 ,6-9 习题6-10,6-12 ,6-15