存在自调用的算法称为递归算法本章主要介绍递归的概念
数据结构(朱战立)章 (6)

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 1 from Peg A to Peg B Move Disk 4 from Peg A to Peg C Move Disk 1 from Peg B to Peg C Move Disk 2 from Peg B to Peg A Move Disk 1 from Peg C to Peg A
" from peg ", fromPeg, " to peg
/*把n-1个圆盘从auxPeg借助fromPeg移至
29
towers(n-1,auxPeg,toPeg,fromPeg); }设计一个测试主函数如下: #include <stdio.h> void main(void) {
Towers(4, 'A', 'C', 'B'); }程序运to Peg B
26
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
并不是每个问题都适宜于用递归算法求解。 适宜于用递归 算法求解的问题的充分必要条件是:
06递归算法PPT课件

递归的定义
若一个对象部分地包含它自己, 或用它自己给自 己定义, 则称这个对象是递归的;若一个过程直接地 或间接地调用自己, 则称这个过程是递归的过程。
递归的定义
直接递归 fun_a() {… fun_a() … }
间接递归 fun_a() {… fun_b() …}
fun_b() {… fun_a() …}
c1, c2,……,cm是若干个可以直接(用非递归方法)解决的问题, g是一个非递归函数,反映了递归问题的结构。
递归模型
例如,阶乘函数 递归出口
1,
当n0时
n! n(n1)!, 当n1时
递归体
ห้องสมุดไป่ตู้
递归的执行过程
实际上,递归是把一个不能或不好直接求解的 “大问题”转化为一个或几个“小问题”来解决,再 把这些“小问题”进一步分解成更小的“小问题”来 解决,如此分解,直至每一个“小问题”都可以直接 解决(此时分解到递归出口)。
递归调用执行过程:
... ...
main() x=17
bn=bSearch(a,x,0,7)
...
bn=bSearch(a,x,0,7)
mid=3 bn=bSearch(a,x,4,7)
...
4
...
return(bn=bSearch(a,x,4,7))
mid=5
bn=bSearch(a,x,4,4)
6.1 递归的概念
若一个算法直接地或间接地调用自己本身,则称 这个算法是递归算法。
1.问题的定义是递归的
例如:阶乘函数的定义
1
当n=0时
n=
n*(n-1) 当n>0时
2. 问题的解法存在自调用 例如:折半查找算法
递归算法知识点总结

递归算法知识点总结一、基本概念递归算法的基本概念是基于递归函数的思想。
递归函数是一个调用自身的函数。
递归算法通常可以分为两种类型:简单递归和复杂递归。
简单递归是指在递归函数中直接调用自身,而复杂递归则是指在递归函数中可能会有多个递归调用。
递归算法通常用于解决可以分解为若干子问题的问题,这种方法通常可以更加简洁地解决问题,但同时也可能会带来一些计算复杂度的问题。
递归算法的设计通常包括以下几个步骤:1. 确定基本情况:在设计递归函数时,通常需要确定一个或多个基本情况。
基本情况通常是指在递归函数中不需要再次调用自身的情况。
2. 确定递归情况:在设计递归函数时,需要确定一个或多个递归情况。
递归情况通常是指在递归函数中需要调用自身的情况。
3. 确定递归方式:当确定了递归函数的基本情况和递归情况之后,就需要确定递归函数的调用方式。
通常有两种方式:直接递归和间接递归。
4. 编写递归函数:根据确定的基本情况、递归情况和递归方式,编写递归函数。
5. 测试递归函数:编写递归函数后,需要对递归函数进行测试,确保递归函数能够正确地解决问题。
二、递归算法的原理递归算法的原理是基于递归函数的调用。
当一个递归函数被调用时,它会将自身的执行环境保存到栈中,并且在栈中分配一些空间。
在递归函数中,如果有一些局部变量,这些变量会在栈中分配空间。
随着递归函数的深入调用,栈中的空间也会不断增加。
在递归函数的执行过程中,通常需要考虑递归栈的压栈和出栈操作。
在递归函数被调用时,会执行一些初始化操作,并将递归参数保存到栈中。
在递归函数中,如果遇到递归情况,会再次调用自身,并且将自身的执行环境保存到栈中。
在递归函数的执行过程中,如果遇到基本情况,就会结束当前递归调用,并且从栈中释放空间。
递归算法的原理是基于递归函数的深度调用的。
当递归函数被调用时,会执行一些初始化过程,并将递归参数保存到栈中。
当递归函数执行完毕后,会从栈中释放空间。
在递归函数的执行过程中,栈中的空间会不断增加和释放。
计算机专业课《算法》_第二章 递归与分治策略

“Hanoi 塔”问题演示 a 初始 a 步骤1 a
c
b
c
“Hanoi 塔”问题程序
void hanoi(int n,a,b,c)
{ if n == 1 move( 1, a, b );
else { hanoi( n-1, a, c, b );
move(n, a, b ); hanoi( n-1, c,b, a) ;
• 递归优点:结构清晰,可读性强
• 递归缺点:递归算法的运行效率较低,无论是耗 费的计算时间还是占用的存储空间都比非递归算 法要多。
整数划分问题的递归关系q(n,m)
如设p(n)为正整数n的划分数,则难以找到递归关系 • q(n,m):正整数n的不同的划分中,最大加数不 大于m的划分个数个数 q(n,m)=
1 q(n,n) 1+q(n,n-1) q(n,m-1)+q(n-m,m) n=1, m=1 n<m n=m n>m>1
递归函数举例(5)
学习要点
理解递归的概念。 掌握设计有效算法的分治策略。
通过典型范例,学习分治策略设计技巧。
2.1 递归的概念
• 递归算法:一个直接或间接地调用自身的算法 • 递归方程:对于递归算法,一般可把时间代 价表示为一个递归方程 • 递归函数:使用函数自身给出定义的函数 • 解递归方程最常用的方法是进行递归扩展
递归函数举例(1)
• 阶乘函数 n !=
1 n(n-1)! n=1 n>1
• Fibonacci数列
1 n=0
F(n)=
1 F(n-1)+F(n-2)
n=1 n>1
初始条件与递归方程是递归函数的二个要素
算法之2章递归与分治

算法分析(第二章):递归与分治法一、递归的概念知识再现:等比数列求和公式:1、定义:直接或间接地调用自身的算法称为递归算法。
用函数自身给出定义的函数称为递归函数。
2、与分治法的关系:由分治法产生的子问题往往是原问题的较小模式,这就为使用递归技术提供了方便。
在这种情况下,反复应用分治手段,可以使子问题与原问题类型一致而其规模却不断缩小,最终使子问题缩小到很容易直接求出其解。
这自然导致递归过程的产生。
分治与递归经常同时应用在算法设计之中,并由此产生许多高效算法。
3、递推方程:(1)定义:设序列01,....na a a简记为{na},把n a与某些个()ia i n<联系起来的等式叫做关于该序列的递推方程。
(2)求解:给定关于序列{n a}的递推方程和若干初值,计算n a。
4、应用:阶乘函数、Fibonacci数列、Hanoi塔问题、插入排序5、优缺点:优点:结构清晰,可读性强,而且容易用数学归纳法来证明算法的正确性,因此它为设计算法、调试程序带来很大方便。
缺点:递归算法的运行效率较低,无论是耗费的计算时间还是占用的存储空间都比非递归算法要多。
二、递归算法改进:1、迭代法:(1)不断用递推方程的右部替代左部(2)每一次替换,随着n的降低在和式中多出一项(3)直到出现初值以后停止迭代(4)将初值代入并对和式求和(5)可用数学归纳法验证解的正确性2、举例:-----------Hanoi塔算法----------- ---------------插入排序算法----------- ()2(1)1(1)1T n T nT=−+=()(1)1W n W n nW=−+−(1)=021n-23()2(1)12[2(2)1]12(2)21...2++2 (121)n n n T n T n T n T n T −−=−+=−++=−++==++=−(1)2 ()(1)1((n-2)+11)1(2)(2)(1)...(1)12...(2)(1)(1)/2W n W n n W n n W n n n W n n n n =−+−=−−+−=−+−+−==++++−+−=−3、换元迭代:(1)将对n 的递推式换成对其他变元k 的递推式 (2)对k 进行迭代(3)将解(关于k 的函数)转换成关于n 的函数4、举例:---------------二分归并排序---------------()2(/2)1W n W n n W =+−(1)=0(1)换元:假设2kn =,递推方程如下()2(/2)1W n W n n W =+−(1)=0 → 1(2)2(2)21k k k W W W−=+−(0)=0(2)迭代求解:12122222321332133212()2(2)212(2(2)21)212(2)22212(2)2*2212(2(2)21)2212(2)222212(2)3*2221...2(0)*2(22...21)22k k k k k k k k k k k k k k k k k k k k k k k k W n W W W W W W W W k k −−−−−−−+−+−−−=+−=+−+−=+−+−=+−−=+−+−−=+−+−−=+−−−==+−++++=−1log 1n n n +=−+(3)解的正确性—归纳验证: 证明递推方程的解是()(1)/2W n n n =−()(1)1W n W n n W =−+−(1)=0,(n 1)=n +n=n(n-1)/2+n =n[(n-1)/2+1]=n(n+1)/2n W W +方法:数学归纳法证 n=1,W(1)=1*(1-1)/2=0假设对于解满足方程,则()---------------快速排序--------------------->>>平均工作量:假设首元素排好序在每个位置是等概率的112()()()(1)0n i T n T i O n n T −==+=∑ >>>对于高阶方程应该先化简,然后迭代(1)差消化简:利用两个方程相减,将右边的项尽可能消去,以达到降阶的目的。
递归

17
n
n-1 n-1
n-1
……
n-2 n-2
n-2
n-2 n-2
n-2
…… 例4 排列问题
1
n0
F
(n)
1
n 1
F (n 1) F (n 2) n 1
边界条件 递归方程
非递归定义:
F(n)
1 5
1
2
5 n1
1
2
5
n1
7
第n个Fibonacci数可递归地计算如下: int fibonacci(int n)
{ if (n <= 1) return 1; return fibonacci(n-1)+fibonacci(n-2);
q(6,1) q(6,2) q(6,3) q(6,4) q(6,5) q(6,6)
28
用栈模拟q(6,6)的递归计算过程。
q(4,1) q(4,2) q(6,2) q(6,3) q(6,4) q(6,5) q(6,6)
29
用栈模拟q(6,6)的递归计算过程。
q( q(6,5) q(6,6)
第2章 递归与分治策略
学习要点: • 理解递归的概念。 • 掌握设计有效算法的分治策略。 • 通过下面的范例学习分治策略设计技巧。 (1)二分搜索技术; (2)大整数乘法; (3)Strassen矩阵乘法; (4)棋盘覆盖; (5)合并排序和快速排序; (6)线性时间选择; (7)最接近点对问题; (8)循环赛日程表。
递归算法

4563697
4564531 4565926
正中间 的元素
4566088
4572874
17
4120243
4276013
4328968 4397700
4462718
请问: 4565926是否在 此列表当中? 4565925?
4466240 4475579
4478964
4480332 4494763
4499043
相应的参数来完成,这就是函数或子程序,使用时只需对其名字进行
简单调用就能来完成特定功能。
例如我们把上面的讲故事的过程包装成一个函数,就会得到:
void Story() { puts("从前有座山,山里有座庙,庙里有个老和尚,老和尚在讲故 事,它讲的故事是:"); getchar();//按任意键听下一个故事的内容 Story(); //老和尚讲的故事,实际上就是上面那个故事 }
4563697
4564531 4565926
4566088
4572874
16
4120243
4276013
4328968 4397700
4462718
请问: 4565926是否在 此列表当中?
4466240 4475579
4478964
4480332 4494763
4499043
4508710 4549243
(1)对原问题f(s)进行分析,假设出合理的“较小 问题” f(s')( 与数学归纳法中假设 n=k-1时等式 成立相似); (2)假设f(s')是可解的,在此基础上确定f(s)的解, 即给出 f(s) 与 f(s') 之间的关系 ( 与数学归纳法中 求证n=k时等式成立的过程相似); (3)确定一个特定情况(如f(1)或f(0))的解,由此 作为递归边界(与数学归纳法中求证n=1时等式 成立相似)。
什么是递归算法

什么是递归算法?
递归算法:是一种直接或者间接地调用 自身的算法。在计算机编写程序中,递归 算法对解决一大类问题是十分有效的,它 往往使算法的描述简洁而且易于理解。
斐波那契的兔子Байду номын сангаас题
某人有一对兔子饲养在围墙中,如果 它们每个月生一对兔子,且新生的兔子在 第二个月后也是每个月生一对兔子,问一 年后围墙中共有多少对兔子。
(1)分析问题
我们可以根据题意列出表来解决这个问题:
兔子问题分析表
交流:
仔细研究兔子问题分析表,你有些什么 发现?每一个月份的大兔数、小兔数与上 一个月的数字有什么联系,能肯定这个规 律吗?
(2)设计算法。 “兔子问题”很容易列出一条递推式而得到 解决。假设第N个月的兔子数目是F(N),我们有:
递归算法所体现的“重复”一般有三个要求: 一是每次调用在规模上都有所缩小(通常是减半); 二是相邻两次重复之间有紧密的联系,前一次要为 后一次做准备(通常前一次的输出就作为后一次的 输入); 三是在问题的规模极小时必须用直接给出解答而不 再进行递归调用,因而每次递归调用都是有条件 的(以规模未达到直接解答的大小为条件),无条 件递归调用将会成为死循环而不能正常结束。
(4)调试程序 因为这个算法的效率不高,建议在调试 程序时月份数不要大于40。
知识迁移:
(1)利用递归方法编写一求N的阶乘。 分析: 根据N!=N*(N-1)*(N-2)*(N-3)*……*3*2*1 可以推出下列式子:
Function F(ByVal n As Integer) As Long If n = 1 Then F = 1 Else F = n * F(n - 1) End Function Private Sub Form_Click() Dim n As Integer n = Val(InputBox("请输入正整数N:", "求N 的阶乘")) Print " 输入的正整数是"; n; Print ",阶乘是"; F(n) End Sub
递归算法的概念

递归算法是一种解决问题的方法,其特点是在解决问题的过程中调用自身。
通常情况下,一个递归算法包括两部分:基本情况和递归情况。
基本情况指的是当问题可以直接解决时的情况,这样的情况下递归算法可以直接返回
结果,而无需再次调用自身。
递归情况指的是将原始问题转化为更小规模的相似问题,并通过调用自身来解决这些更小规模的问题。
递归算法通常用于解决可以被分解为相同类型子问题的问题,比如树结构、图搜索、
分治算法等。
在实现递归算法时,需要小心处理递归调用的终止条件,以避免出现无
限循环的情况。
虽然递归算法可以让问题的解决方法更加清晰和简洁,但有时候也可能会导致效率低
下或者栈溢出的问题。
因此,在使用递归算法时需要谨慎考虑,并且有时候可以通过
迭代的方式来替代递归算法。
递归算法分析

已知X和n的值,求Xn的值
传统做法:for一遍,计算n次乘积,复杂度? 朴素快速幂做法:折半计算,复杂度?
问题就是排序
在700多年前,意大利有一位著名数学家斐波那契在他的《算盘全集》一书中 提出了这样一道有趣的兔子繁殖问题。 如果有一对小兔,每一个月都生下一对小兔,而所生下的每一对小兔在出生 后的第三个月也都生下一对小兔。那么,由一对兔子开始,满一年时一共可以 繁殖成多少对兔子? 用列举的方法可以很快找出本题的答案: 第一个月,这对兔子生了一对小兔,于是这个月共有2对(1+1=2)兔子。 第二个月,第一对兔子又生了一对兔子。因此共有3对(1+2=3)兔子。 第三个月,第一对兔子又生了一对小兔而在第一个月出生的小兔也生下了一对 小兔。所以,这个月共有5对(2+3=5)兔子。 第四个月,第一对兔子以及第一、二两个月生下的兔子也都各生下了一对小兔 。因此,这个月连原先的5对兔子共有8对(3+5=8)兔子。 列表如下:
递归模型是递归算法的抽象 , 它反映一个递归 问题的递归结构,例如,前面的递归算法对应的 递归模型如下:
fun(1)=1 fun(n)=n*fun(n-1) (1) n>1 (2)
其中 , 第一个式子给出了递归的终止条件 , 第二个式子给出了 fun(n) 的值与 fun(n-1) 的值 之间的关系,我们把第一个式子称为递归出口, 把第二个式子称为递归体。
1、阶乘 2、求数列前n项和 3、折半查找 4、快速幂 5、斐波那契数列 6、汉诺塔问题 7、快速排序
[分析] n!的计算是一个典型的递归问题。使用递归方法来描 述程序,十分简单且易于理解。 [步骤1] 描述递归关系 。 递归关系是这样的一种关系。设{U1,U2,U3,…,Un…}是一个序 列,如果从某一项k开始,Un和它之前的若干项之间存在一 种只与n有关的关系,这便称为递归关系。
递归算法

return knap(m,n-1); }
3.递归算法设计
递归算法
算法设计和分析
递归算法
Hanoi塔问题
汉诺塔(Tower of Hanoi)游戏据说来源于布拉玛神庙。游戏的 装置如图所示(图上以3个金片例),底座上有三根金的针,第 一根针上放着从大到小64个金片。游戏的目标是把所有金片从 第一根针移到第三根针上,第二根针作为中间过渡。每次只能
建立标号:分别在过程的第一条可执行语句处、每个递归调
用处建立标号,依次为:L0,L1,L2,……,做为入口地址和返 回地址
消去递归调用:局部变量、形参、返回地址入栈,形式参数赋 值,goto语句到L0
修改函数的返回部分:
• 用户栈为空,返回 • 返回值保存到全局变量中,同时将引用参数赋给栈顶的相应变量
{
CStack<int> stack;
int retvalue,retaddr;
int res ;
L0:
if( a < b )
{
res = GCD(b,a);
L1:
;
}
else if( b == 0 )
{
res = a;
}
else
{
res = GCD(b,a%b);
L2:
;
}
return res; }
}
修改标号L1处的递归调用
算法设计和分析
递归算法
else {
//res = GCD(b,a%b); //保护现场
stack.Push(a); stack.Push(b); stack.Push(res); stack.Push(2); //返回地址 stack.Push(b); stack.Push(a%b); //设置函数的调用参数 goto L0; L2: res = retvalue; //返回值放在全局变量里 }
数据结构-使用C语言 朱战立 第6章递归

6.7设计举例
6.7.1 一般递归算法设计举例
例6-5 设计一个输出如下形式数值的递归算法。 n n n ... n
...... 3 2 1 3 2 3
18
问题分析:该问题可以看成由两部分组成:一部分是输出一 行值为n的数值;另一部分是原问题的子问题,其参数为n-1。 当参数减到0时不再输出任何数据值,因此递归的出口是当参 数n≤0时空语句返回。
( 3 )分析当调用语句为 Gcd(97, 5) 时算法的 执行过程和执行结果;
(4)编写求解该问题的循环结构算法。
23
解:
递归算法如下: 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); }
7
递归算法如下:
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); } /*在上半区查找*/ /*查找成功*/ /*查找不成功*/
Move Disk 2 from Peg A to Peg C
Move Disk 1 from Peg B to Peg C
递归及递归算法分析课件

A(1,2)=A(A(0,2),1)=A(1,1)=2,故A(n,2)= 2^n 。
2 222
❖ M=3时,类似的可以推出
n
❖ M=4时,A(n,4)的增长速度非常快,以至于没有适当的数 学式子来表示这一函数。
❖
move(a,b);
❖
hanoi(n-1, c, b, a);
❖
}
❖}
❖ T(n)=2T(n-1)+O(1) n≥1
T(n)=2n-1
0
n=0
4
27
简单递归式的求解
1.T(n)=T(n-1)+c1 n>1
c2
n=1
2. T(n)=2T(n/2)+c1 n ≥2
c2
n<2
3. T(n)=2T(n/2)+Θ(n) n ≥2
O(1)
n<2
28
T( n/2 ) + T( n/2 ) + 1
例1 T(n) =
0
(n = 1)
解 :T(n)=2T(n/2)+1
=22T(n/22)+2+1
=23T(n/23)+22+2+1
令2r=n =2rT(1)+2r-1+。。。+2+1
=(1-2r)/(1-2)=n-1
∴ T( n ) = n - 1
25
递归算法的时间复杂度分析
❖ 递归函数求解
简单递归式求解 master method 递推方程的特征方程求解
递归的概念

递归的概念
一、递归(R e c u r s i o n)的概念
如果一个对象部分地由自己组成,或者是按它自已定义的,则称为是递归的。
递归不仅在数学中会遇到,在日常生活中也可以遇到。
请看下面的图片:
二、递归算法
直接或间接地调用自身的算法称为递归算法。
用函数自身给出定义的函数称为递归函数。
在计算机算法设计与分析中,递归技术是十分有用的。
使用递归技术往往使函数的定义的算法的描述简洁且易于理解。
有些数据结
构如二叉树等,由于其本身固有的递归特性,特别适合用递归的形
式来描述。
另外,还有一些问题,虽然其本身并没有明显的递归结构,但用递归技术来求解使设计出的算法简洁易懂且易于分析。
递归算法一般用于解决三类问题:
(1)数据的定义形式是按递归定义的。
这类递归问题往往又可转化成递推算法,递归边界作为递推的边界条件。
比如阶乘的定义:
(式
2-1)
又如裴波那契(F i b o n a c c i)数列的定义:
(式
2-2)
(2)问题解法按递归算法实现。
例如回溯等。
(3)数据的结构形式是按递归定义的。
如树的遍历,图的搜索等。
递归算法解题通常显得很简洁,但递归算法解题的运行效率较低。
在递归调用的过程当中系统为每一层的返回点、局部量等开辟
了栈来存储。
递归次数过多容易造成栈溢出等。
数据结构——使用C语言版(朱战立)递归算法

//递归
main(void) { int x=4; Display(x); }
23
练习:给出下列递归函数当n=0,1,2,3,4,5 时的输出值。 long Cfib(int n) { if (n<0) return 0; if (n==0||n==1) return n; else return 4*Cfib(n-2)+5*Cfib(n-1); }
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;
13
测试主函数设计如下:
# 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); }
当一个问题存在上述两个基本要素时,该问题的递归算 法的设计方法是: (1)把对原问题的求解设计成包含有对子问题求解的形 式。 (2)设计递归出口。
11
递归算法的执行过程是不断地自调用,直到到达递归 出口才结束自调用过程;到达递归出口后,递归算法开始 按最后调用的过程最先返回的次序返回;返回到最外层的 调用语句时递归算法执行过程结束。
第6章+递归

从前有座山,山里有座庙,庙里有两个和尚,老和尚对小 和尚说: 从前有座山,山里有座庙,庙里有两个和尚,老和尚对小 和尚说:
……
6.1递归的概念
若一个算法直接的或间接的调用自己本身,则称这 个算法是递归算法。 存在算法调用自己的情况:
阶乘函数的常见定义是:
} //把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);
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; } } //递归调用
6.4递归过程和运行时栈
对于非递归函数,调用函数在调用被调用函数前,系 统要保存以下两类信息: (1)调用函数的返回地址; (2)调用函数的局部变量值。 当执行完被调用函数,返回调用函数前,系统首先要 恢复调用函数的局部变量值,然后返回调用函数的返回 地址。
递归函数被调用时,系统要作的工作和非递归函数 被调用时系统要作的工作在形式上类同,但保存信息的 方法不同。 递归函数被调用时,系统需要一个运行时栈.系统的 运行时栈也要保存上述两类信息。每一层递归调用所需 保存的信息构成运行时栈的一个工作记录,在每进入下 一层递归调用时,系统就建立一个新的工作记录,并把 这个工作记录进栈成为运行时栈新的栈顶;每返回一层 递归调用,就退栈一个工作记录。因为栈顶的工作记录 必定是当前正在运行的递归函数的工作记录,所以栈顶 的工作记录也称为活动记录。
递归及递归算法图解

递归问题的提出
第一步:将问题简化。 – 假设A杆上只有2个圆盘,即汉诺塔有2层,n=2。
A
B
C
递归问题的提出
A
B
C
对于一个有 n(n>1)个圆盘的汉诺塔,将n个圆盘分 为两部分:上面的 n-1 个圆盘和最下面的n号圆盘。将 “上面的n-1个圆盘”看成一个整体。
– 将 n-1个盘子从一根木桩移到另一根木桩上
1
当n 1时
n ! n (n 1)! 当n 1时
long int Fact(int n)
{ long int x;
if (n > 1)
{ x = Fact(n-1);
/*递归调用*/
return n*x; }
else return 1;
/*递归基础*/
}
Fact(n) 开始 传进的参数n N n>1
两种不同的递归函数--递归与迭代
21
(2)递归和迭代有什么差别?
递归和迭代(递推)
迭代(递推):可以自递归基础开始,由前向后依次计算或直
接计算;
递归:可以自递归基础开始,由前向后依次计算或直接计算;
但有些,只能由后向前代入,直到递归基础,寻找一条路径, 然后再由前向后计算。
递归包含了递推(迭代),但递推(迭代)不能覆盖递归。
递归的概念 (5)小结
战德臣 教授
组合 抽象
构造 递归
用递归 定义
用递归 构造
递归计 算/执行
递归 基础
递归 步骤
两种不同的递归函数
递归
迭代
两种不同的递归函数--递归与迭代
20
(1)两种不同的递归函数?
递归和递推:比较下面两个示例
第6章 递归

第6章 递归
用递归方法分析考虑如下。设盘子的总数为n,我 们给A柱上的盘子从上至下编号为1到n。当n=1时, 问题可直接求解,即我们可直接把A柱上的盘子移到 C柱上;当n>1时,移动由以下三步组成: (1)用C柱做过渡把A柱上的n-1个盘子移到B 柱上; (2)把A柱上的最后一个盘子移到C柱上; (3)用A柱做过渡把B柱上的n-1个盘子移 到C柱上。
第6章 递归
*6.5 转化递归算法为非递归算法
总结我们本章至此讨论过的递归算法,可得出递归 算法的两个特性: (1)递归算法是一种分而治之的、把复杂问题分解 为简单问题的求解问题方法,对求解某些复杂问题, 递归算法分析问题的方法是十分有效的。 (2)递归算法的时间效率通常非常差,其时间效率 经常是实际应用中不可忍受的。
第6章 递归
另一个典型的例子是汉诺塔问题的求解。汉诺塔 问题是:设有3根标号为A,B,C的柱子,在A柱上放 着n个盘子,每一个都比下面的略小一点,要求把A柱 上的盘子全部移到C柱上。移动的规则是:(1)一次 只能移动一个盘子;(2)移动过程中大盘子不能放在 小盘子上面;(3)在移动过程中盘子可以放在A,B, C的任意一个柱子上。
第6章 递归
6.5.1 尾递归和单向递归的消除 尾递归是递归调用语句只有一个,而且是处于算法 的最后。以阶乘问题的递归算法Fact(n)为例讨论尾递归 算法的运行过程。为讨论方便,我们再次列出阶乘问题 的递归算法Fact(n),并简化掉参数n的出错检查语句,改 写递归调用语句的位置在最后,算法如下: long Fact(int n) { if(n==0)return1; return n*Fact(n-1); }
第6章 递归
分析上述算法可以发现,当递归调用返回时,返 回到上一层递归调用的下一语句,而这个返回位置正 好是算法的末尾。也就是说,以前每次递使用。因此,对于尾递归形式的递归 算法,不必利用系统的运行时栈保存各种信息。尾递 归形式的算法实际上可变成循环结构的算法。循环结 构的阶乘问题算法Fact2(n)如下:
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
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);
12
基本思想:1个盘子的汉诺塔问题可直接移动。n个盘 子的汉诺塔问题可递归表示为,首先把上边的n-1个盘 子从A柱移到B柱,然后把最下边的一个盘子从A柱移 到C柱,最后把移到B柱的n-1个盘子再移到C柱。4个 盘子汉诺塔问题的递归求解示意图如图6-4所示。
13
图6-4 汉诺塔问题的递归求解示意图
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 -1;
}
if(n == 0) return 1;
else {y = Fact(n - 1); //递归调用
return n * y; }
} 6
设计主函数如下
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所示。
10
6.3递归算法的设计方法
递归算法既是一种有效的算法设计方法,也是一种有效的分析 问题的方法。递归算法求解问题的基本思想是:对于一个较为复杂 的问题,把原问题分解成若干个相对简单且类同的子问题,这样, 原问题就可递推得到解。
适宜于用递归算法求解的问题的充分必要条件是: (1)问题具有某种可借用的类同自身的子问题描述的性质; (2)某一有限步的子问题(也称作本原问题)有直接的解存在。
阶乘函数的常见定义是:
3
也可定义为:
写成函数形式,则为:
这种函数定义的方法是用阶乘函数自己 本身定义了阶乘函数,称公式(6 – 3)是阶 乘函数的递推定义式。
4
(2)问题的解法存在自调用 一个典型的例子是在有序数组中查找一个数据 元素是否存在的折半查找算法。
请见教材134页 图6-1 折半查找过程
return BSearch(a, x, mid+1, high); //在上半区查找
}
8
测试主函数设计如下:
# 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);
}
9
BSearch(a, x, 0,7)的递归调用过程如图6-3所示, 其中,实箭头表示函数调用,虚箭头表示函数的返回值。
请见教材137页: 图6-3 BSearch(a, x, 0,7)的递归调用过程
5
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(“参数错!”);
请见教材136页图6-2 Fact(3)的递归调用执行过程
7
例6-2 给出在有序数组a中查找数据元素x是否存在的 递归算法,并给出如图6-1所示实际数据的递归算法的 执行过程。递归算法如下:
int BSearch(int a[], int x, int low, int high)
{
int mid;
第6章 递归算法
本章导读
存在自调用的算法称为递归算法。本 章主要介绍递归的概念、递归算法的执行 过程、递归算法的设计方法以及递归算法 的效率,递归算法是解决许多复杂应用问 题的重要方法。
1
6.1递归的概念
若一个算法直接的或间接的调用自己本身,则称这 个算法是递归算法。 存在算法调用自己的情况: (1)问题的定义是递推的
14
以三个盘为例:
a
b
c
A
B
C
1
1
2
1
2
13
2
13
X
Y
Z
15
算法设计:首先,盘子的个数n是必须的一个输入参数, 对n个盘子,我们可从上至下依次编号为1,2,…,n;其次, 输入参数还需有3个柱子的代号,我们令3个柱子的参数名 分别为fromPeg,auxPeg和toPeg;最后,汉诺塔问题 的求解是一个处理过程,因此算法的输出是n个盘子从柱 子fromPeg借助柱子auxPeg移动到柱子toPeg的移动步 骤,我们设计每一步的移动为屏幕显示如下形式的信息:
当一个问题存在上述两个基本要素时,该问题的递归算法的设计 方法是: (1)把对原问题的求解设计成包含有对子问题求解的形式。 (2)设计递归出口。
11
例6-3 设计模拟汉诺塔问题求解过程的算法。
汉诺塔问题的描述是:设有3根标号为A,B,C的柱子, 在A柱上放着n个盘子,每一个都比下面的略小一点, 要求把A柱上的盘子全部移到C柱上,移动的规则是: (1)一次只能移动一个盘子;(2)移动过程中大盘 子不能放在小盘子上面;(3)在移动过程中盘子可以 放在A,B,C的任意一个柱子上。 问题分析:可以用递归方法求解n个盘子的汉诺塔问题。
Move Disk i from Peg X to Peg Y 这样,汉诺塔问题的递归算法可设计如下:
16
void towers(int n, char fromPeg, char toPeg, char auxPeg)
{
if(n==1)
//递归出口
{ printf("%s%c%s%c\n", "move disk 1 from peg ",