第九章 递归
递归的工作原理
递归的工作原理
递归是一种通过调用自身来解决问题的方法。
它通过将一个大问题分解为较小的、同类的子问题来进行求解。
递归的工作原理可以概括为以下几个步骤:
1. 定义基准情况:递归函数首先需要定义一个基准情况,即最简单的情况,该情况下可以直接给出结果,而不是再次调用自身。
基准情况通常是在满足某个条件时返回固定值或特定操作。
2. 分解问题:递归函数会将给定的问题分解为更小的同类子问题。
这个过程可以通过递归调用本身来实现。
每次递归调用时,问题的规模都会减小,同时保持相同的问题类型。
3. 调用递归函数:递归函数在解决子问题时通过调用自身来实现。
通过递归调用,问题将会在不断分解和缩小的过程中得到解决。
4. 组合结果:递归函数返回的结果会被用来组合成大问题的解。
递归函数返回的结果通常会在每一层递归结束后进行合并、计算或其他操作。
5. 终止递归:为了防止递归无限循环,递归函数需要在某个条件下终止递归调用。
这个条件通常和基准情况或问题规模相关,当满足条件时,递归将停止执行。
需要注意的是,递归函数的设计需要保证每次递归调用后问题规模都会减小,否则递归可能会陷入无限循环,导致程序运行
出错或引起栈溢出。
此外,递归可能会造成重复计算,因此可以采用记忆化搜索等方法进行优化。
递归
①
编程思路
采用实例3-3的递归产生10位二进制数的全 排列,每位二进制数码分别对应一个问题 的解答情况,0代表答错,1代表回答正确。 若计算出的分数恰好为100,则就是问题的 一组解。
【实例3-5】取值组合
有一个集合拥有m个元素{1,2,…,m}, 任意的从集合中取出n个元素,则这n个元 素所形成的可能子集有哪些?编写一个程 序,输入整数m和n,输出m个元素中,取 出n个元素的各种情况。
使用递归的典型情况
Biblioteka 递归方法通过函数调用自身将问题转化为 本质相同但规模较小的子问题,是分治策 略的具体体现。 递归使用情形
问题的定义是递归的 数据结构是递归的 问题的求解过程是递归的
递归模型
递归模型由递归出口和递归体两部分组成, 前者确定递归到何时结束,后者确定递归 求解时的递推关系 递归的思路是把一个不能或者不好直接求 解的“大问题”转化为一个或者几个“小 问题”来解决;“小问题”进一步分解为 更小的“小问题”;如此分解,直到“小 问题”可以直接求解
将步骤1和步骤2中的递归关系与边界统一起来用数学语 言来表示,即 n!= n*(n−1)! 当n>1时 n!= 1 当n=1时 再将这种关系翻译为代码,即一个函数:
long f(int n){ long g; if (n<0) cout << "n<0, 输入错误!"; else if (n == 1) g = 1; else g = n*f(n-1); return g; }
1 2 3 N 木桩A 木桩B 1 2 3 N 木桩A 木桩B 木桩C 木桩C
编程思路
设递归函数hanoi(n,a,b,c)展示把n个盘从A 柱借助B柱移到C柱的过程
第九章 递归
Hanoi( n-1,c,b,a); }
汉诺塔问题
void main( ) { int N; cout << "Please input disc number: " << endl; cin >> N; cout << "The solution is:" << endl; Hanoi( N,'A','B','C'); }
//先将n 个盘子, //先将n-1个盘子,以b为中转,从a柱移动到c柱, 先将 为中转, 柱移动到c
Hanoi( n-1,a,c,b);
//将一个盘子从a移动到b //将一个盘子从a移动到b 将一个盘子从
cout << a << "->" << b << endl;
//将 柱上的n 个盘子, //将c柱上的n-1个盘子,以a为中转,移动到b柱 为中转,移动到b
逆波兰表达式
问题描述
–逆波兰表达式是一种把运算符前置的算术表达式,例如2+3 逆波兰表达式是一种把运算符前置的算术表达式,例如2+3 逆波兰表达式是一种把运算符前置的算术表达式 的逆波兰表示法为+ 3。 的逆波兰表示法为+ 2 3。 –逆波兰表达式的优点是运算符之间不必有优先级关系,也 逆波兰表达式的优点是运算符之间不必有优先级关系, 逆波兰表达式的优点是运算符之间不必有优先级关系 不须用括号,例如(2+3)*4的逆波兰表示法为* (2+3)*4的逆波兰表示法为 4。 不须用括号,例如(2+3)*4的逆波兰表示法为* + 2 3 4。 –输入并求解逆波兰表达式的值,其中运算符包括+ - * /。 输入并求解逆波兰表达式的值,其中运算符包括+ /。 输入并求解逆波兰表达式的值
数据结构之递归
数据结构之递归Ⅲ递归的三⼤要素// 算 n 的阶乘(假设n不为0)int f(int n){if(n <= 2){return n;}}第三要素:找出函数的等价关系式第三要素就是,我们要不断缩⼩参数的范围,缩⼩之后,我们可以通过⼀些辅助的变量或者操作,使原函数的结果不变。
例如,f(n) 这个范围⽐较⼤,我们可以让 f(n) = n * f(n-1)。
这样,范围就由 n 变成了 n-1 了,范围变⼩了,并且为了原函数f(n) 不变,我们需要让 f(n-1) 乘以 n。
说⽩了,就是要找到原函数的⼀个等价关系式,f(n) 的等价关系式为 n * f(n-1),即f(n) = n * f(n-1)。
这个等价关系式的寻找,可以说是最难的⼀步了,如果你不⼤懂也没关系,因为你不是天才,你还需要多接触⼏道题,我会在接下来的⽂章中,找 10 道递归题,让你慢慢熟悉起来。
找出了这个等价,继续完善我们的代码,我们把这个等价式写进函数⾥。
如下:// 算 n 的阶乘(假设n不为0)int f(int n){if(n <= 2){return n;}// 把 f(n) 的等价操作写进去return f(n-1) * n;}⾄此,递归三要素已经都写进代码⾥了,所以这个 f(n) 功能的内部代码我们已经写好了。
这就是递归最重要的三要素,每次做递归的时候,你就强迫⾃⼰试着去寻找这三个要素。
还是不懂?没关系,我再按照这个模式讲⼀些题。
有些有点⼩基础的可能觉得我写的太简单了,没耐⼼看?少侠,请继续看,我下⾯还会讲如何优化递归。
当然,⼤佬请随意,可以直接拉动最下⾯留⾔给我⼀些建议,万分感谢!Ⅲ案例1:斐波那契数列斐波那契数列的是这样⼀个数列:1、1、2、3、5、8、13、21、34....,即第⼀项 f(1) = 1,第⼆项 f(2) = 1.....,第 n 项⽬为 f(n) = f(n-1) + f(n-2)。
求第 n 项的值是多少。
递归 高等数学
递归与高等数学一、递归函数递归函数是一种数学函数,它在其定义或行为中直接或间接地调用自身。
递归函数通常用于解决一些可以分解为更小的子问题的问题。
递归函数可以分为两类:基本递归函数和递归函数。
基本递归函数是直接解决问题的函数,而递归函数则是通过调用自身来解决问题的函数。
在高等数学中,许多问题可以通过使用递归函数来解决。
例如,在微积分中,许多积分和级数可以通过递归方法进行计算。
此外,在实数和复数分析中,许多函数可以通过递归函数进行展开和逼近。
二、递归数列递归数列是一种特殊的数列,它可以通过一系列规则生成。
常见的递归数列包括斐波那契数列、卢卡斯数列等。
递归数列在数学和计算机科学中都有广泛的应用。
在高等数学中,递归数列可以用于解决一些与序列相关的问题。
例如,在概率论和统计学中,一些概率分布可以通过递归数列进行描行解决。
三、递归方程递归方程是一种描述自然规律的数学工具,它是通过递归函数定义的等式或系统。
常见的递归方程包括人口动态模型、斐波那契序列等。
在高等数学中,递归方程可以用于解决一些与时间相关的问题。
例如,在微分方程和差分方程中,一些问题可以通过递归方程进行描述和解决。
此外,在控制理论和系统理论中,一些系统可以通过递归方程进行建模和分析。
四、递归级数递归级数是具有特定模式的数字序列或数字集的级数表示。
它与级数、级数定理、积分级数以及算术、几何和三角级数等都有密切的关系。
在高等数学中,递归级数可以用于解决一些与数字相关的问题。
例如,在离散概率论和统计学中,一些概率分布可以通过递归级数进数进行解决。
五、递归图论图论是研究图(由顶点和边构成的图形)的数学理论。
在图论中,图是由顶点(或节点)和连接这些顶点的边构成的。
递归图论则是使用递归来定义或描述图的理论。
在计算机科学中,这可以用于计算机算法、数据结构和其他相关的领域。
例如,一种常用的数据结构是二叉堆(Binary Heap),它可以看作是一个完全二叉树,并且每个节点都有两个子节点(除了叶节点)。
递归
递归实例4
循环日程表问题。 n=8个运动员的日程表,填充方式见下面
一个8*8(23的方阵) 的矩阵,可以分为4*4(22的方阵)的 四个矩阵,设其分别A,B,C,D,则A=B,C=D=A+4
递归实例4
循环日程表问题
void circulateSchedule(int n) { // 递归出口 if(n == 1) { return ; } // 将2^k*2^k的表格分成2^(k-1)*2^(k-1)的四个子表格,构造第1个子表 int half = n / 2; circulateSchedule( half); // 填充余下三个子表 for(int i=0; i<half;i++) for(int j=0;j<half;j++) { table[half+i][half+j] = table[i][j]; table[i][ half+j] = table[ half+i][j] = table[i][j] + half; }
“汉诺塔”(Hanoi)
这是一个必须用递归方法才能解决的问题
n=64时, 18,446,744,073,709,551,615次 1844亿亿次 每次1微秒,需要60万年
递归问题分析
第一步:将问题简化。
假设A杆上只有2个圆盘,即汉诺塔有2层,n=
2。 A
B
C
递归问题分析
递归实例3——解决复杂问题
汉诺塔——汉诺塔问题是最经典的只能够使用
递归的方法解决的问题,题目描述如下:
据传说,在古代世界中心的贝拿勒斯(印度北部)的圣庙 里,一块在黄铜 板上插着3根宝石针。印度教的主神梵天 在创造世界时,在其中的一根针上自下而上地穿好了由大 至小的64层金片,即为汉诺塔。无论白天黑夜,总有一个 僧侣按 如下的法则移动这些金片,一次只能够移动一层 ,不管在哪根针上,小片必须在大片的上面。 要求借助 于第二根针将整个汉诺塔移至第三根针上。
数据结构(C++)--递归
数据结构(C++)..递归数据结构(C++)..递归1.什么是递归递归是指在函数的定义中调用函数本身的情况。
通过递归,可以解决一些问题,特别是那些问题的解决方法和问题的子问题的解决方法相同或相似的情况。
递归的思想是将大问题拆分成小问题,然后通过解决小问题来解决大问题。
递归过程中,函数会不断调用自身,直到达到某个终止条件。
2.递归的基本要素●递归函数的定义:递归函数是指在函数中调用自身的函数。
●终止条件:递归函数必须有一个终止条件,以终止递归过程,避免无限递归。
●递归调用:递归函数在函数体中调用自身。
3.递归的实现递归可以用于解决很多问题,例如计算阶乘、斐波那契数列等。
3.1 计算阶乘```cppint factorial(int n) {if (n == 0 .......●n == 1) {return 1。
}return n factorial(n.1)。
}```在上述代码中,计算阶乘的递归函数`factorial`通过调用自身来实现。
当n为0或1时,递归终止,返回1。
否则,递归调用`factorial(n.1)`来计算(n.1)的阶乘,并将结果乘以n。
3.2 斐波那契数列```cppint fibonacci(int n) {if (n == 0 .......●n == 1) {return n。
}return fibonacci(n.1) + fibonacci(n.2)。
}```在上述代码中,计算斐波那契数列的递归函数`fibonacci`通过调用自身来实现。
当n为0或1时,递归终止,返回n。
否则,递归调用`fibonacci(n.1)`和`fibonacci(n.2)`来计算前两个数的和。
4.递归的优缺点4.1 优点●代码简洁:递归能将问题简化成更小的问题,提高代码的可读性和可维护性。
●解决复杂问题:递归能解决一些复杂的问题,例如树的遍历、图的搜索等。
4.2 缺点●递归调用会占用大量的栈空间,导致内存消耗较大。
2017-递归
例. Hanoi塔的迭代算法,m是原柱上圆盘的个数 算法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(1, i, j, k).
IF p = NULL THEN ( PRINT “Can not find the item”. RETURN. )
S2. IF data(p) = item THEN Dealwith(p). // 通过Dealwith函数 // 对该节点进行一定的处理 ELSE Search( next(p), item).
void Hanoi (int n, String A, String B, String C ) { //解决汉诺塔问题的算法
if ( n == 1 ) cout << "move" << A << " to " << C << endl;
else { Hanoi ( n-1, A, C, B ); cout << " move " << A << " to " << C << endl; Hanoi ( n-1, B, A, C ); }
最后一次发生的递归过程必须最先完成。
例. 计算数组 A 中最大最小元素的算法BS 。 算法BS(A ,i ,j . fmax ,fmin) /* 在数组A的第i个元素到第j个元素之间寻找最大和
递归算法详解
递归冯文科一、递归的基本概念。
一个函数、概念或数学结构,如果在其定义或说明内部直接或间接地出现对其本身的引用,或者是为了描述问题的某一状态,必须要用至它的上一状态,而描述上一状态,又必须用到它的上一状态……这种用自己来定义自己的方法,称之为递归或递归定义。
在程序设计中,函数直接或间接调用自己,就被称为递归调用。
二、递归的最简单应用:通过各项关系及初值求数列的某一项。
在数学中,有这样一种数列,很难求出它的通项公式,但数列中各项间关系却很简单,于是人们想出另一种办法来描述这种数列:通过初值及a与前面临n近几项之间的关系。
要使用这样的描述方式,至少要提供两个信息:一是最前面几项的数值,一是数列间各项的关系。
比如阶乘数列1、2、6、24、120、720……如果用上面的方式来描述它,应该是:如果需要写一个函数来求a的值,那么可以很容易地写成这样:n这就是递归函数的最简单形式,从中可以明显看出递归函数都有的一个特点:先处理一些特殊情况——这也是递归函数的第一个出口,再处理递归关系——这形成递归函数的第二个出口。
递归函数的执行过程总是先通过递归关系不断地缩小问题的规模,直到简单到可以作为特殊情况处理而得出直接的结果,再通过递归关系逐层返回到原来的数据规模,最终得出问题的解。
以上面求阶乘数列的函数)f为例。
如在求)3(f时,由于3不是特殊值,因(n此需要计算)2(*3f,但)2(f是对它自己的调用,于是再计算)2(f,2也不是特殊值,需要计算)1(2f,需要知道)1(f的值,再计算)1(f,1是特殊值,于是直*接得出1)2(=2f,再返回上一步,得=f)1(*)1(=f,返回上一步,得23)3(=f,从而得最终解。
=f=*6)2(23*用图解来说明,就是下面再看一个稍复杂点的例子。
【例1】数列}{n a 的前几项为1、111+、11111++、1111111+++、……输入n ,编程求n a 的精确分数解。
分析:这个题目较易,发现11=a ,其它情况下有111-+=n n a a 。
递归的概念
递归的概念程序调用自身的编程技巧称为递归(recursion)。
递归作为一种算法在程序设计语言中广泛应用。
一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的能力在于用有限的语句来定义对象的无限集合。
一般来说,递归需要有边界条件、递归前进段和递归返回段。
当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
递归定义递归,就是在运行的过程中调用自己。
函数嵌套调用过程示例构成递归需具备的条件:1. 子问题须与原始问题为同样的事,且更为简单;2. 不能无限制地调用本身,须有个出口,化简为非递归状况处理。
在数学和计算机科学中,递归指由一种(或多种)简单的基本情况定义的一类对象或方法,并规定其他所有情况都能被还原为其基本情况。
例如,下列为某人祖先的递归定义:某人的双亲是他的祖先(基本情况)。
某人祖先的双亲同样是某人的祖先(递归步骤)。
斐波纳契数列(Fibonacci Sequence),又称黄金分割数列,指的是这样一个数列:1、1、2、3、5、8、13、21 (I)斐波纳契数列是典型的递归案例:递归关系就是实体自己和自己建立关系。
Fib(0) = 1 [基本情况] Fib(1) = 1 [基本情况] 对所有n > 1的整数:Fib(n) = (Fib(n-1) + Fib(n-2)) [递归定义] 尽管有许多数学函数均可以递归表示,但在实际应用中,递归定义的高开销往往会让人望而却步。
例如:阶乘(1) = 1 [基本情况] 对所有n > 1的整数:阶乘(n) = (n * 阶乘(n-1)) [递归定义] 一种便于理解的心理模型,是认为递归定义对对象的定义是按照“先前定义的”同类对象来定义的。
例如:你怎样才能移动100个箱子?答案:你首先移动一个箱子,并记下它移动到的位置,然后再去解决较小的问题:你怎样才能移动99个箱子?最终,你的问题将变为怎样移动一个箱子,而这时你已经知道该怎么做的。
递归名词解释
递归名词解释
递归:
1、概念:递归是一种反复出现的算法,它将一个大问题拆分成若干小
问题,并且使用结果解决大问题,在解决大问题时候可以多次重复调
用自身以达到解决问题的目的。
2、定义:递归是一种编程技术,它提供了一种可以让程序创建动态的、多层的数据结构的一种方法,也就是用较少的语句可以实现数据结构
的表示。
3、原理:递归调用的原理是多次执行同一个程序段,在程序段内部,
对已知的特殊情况保存结果,减少运算量,并根据条件调用自身以解
决整个问题。
4、特点:
* (1)可以根据已知条件简化问题
* (2)可以多次调用自身、产生迭代结果,在一次调用中展开多次迭代,以实现复杂数据结构的表示
* (3)可以在程序段中使用条件语句,在特定条件真伪时返回特定的结
果
* (4)可以实现交替操作,多次调用自身,每次执行的逻辑可能不一样。
5、应用场景:
* (1)求解较复杂的数学问题,比如斐波那契数列问题、汉诺塔问题等* (2)求解复杂算法,比如快速排序、归并排序等
* (3)实现迭代计算,让程序可以定义无限长度的数据结构。
递归
4
从计算的角度看,给出的递归定义也提供了一种计算阶乘 函数的方法。 如果用来描述计算过程的工具(程序语言)能够支持用递 归的方式定义计算过程,递归定义就可以直接翻译成一个 程序。
5
二. 递归函数
函数在自身定义的内部直接或间接调用自己。
【例1】编fac(n)=n! 的递归函数
n0 1 fac(n) 1 n * fac(n 1) n =
当 n>1
3 21 144 987 6765 46368 317811 2178309 14930352 102334155
1 5 34 233 1597 10946 75025 514229 3524578 24157817
24
递归方式
采用递归方式,可以很容易实现求 Fn 的函数。 function fib(n) begin if n<2 then return 1 else fib(n-1)+fib(n-2) 硬性把负数编号的 序列值定义为 1
7, 6+1, 5+2,5+1+1 4+3,4+2+1,4+1+1+1 3+3+1,3+2+2,3+2+1+1,3+1+1+1+1 2+2+2+1,2+2+1+1+1,2+1+1+1+1+1 1+1+1+1+1+1 例如,n=6,n1=2
18
2+2+2+1,2+2+1+1+1,2+1+1+1+1+1
递归
递归递归是程序设计中重要的手段,在深搜、及后继的树、图等的学习中必用的手段。
递归的教学重在讲清楚思路。
通过递推关系把原来问题缩小成一个更小规模的同类问题,并延续这一缩小规模的过程,直到在某一规模上,问题的解是已知的。
这样一种解决问题的思想我们称为递归的思想。
分析递归时不可追究子问题的细节。
递归问题可以由栈来完成。
递归的教学要由浅入深。
有下列的一些例子。
(SMP子问题区)1.公式类的递归, 由自己推的公式进行递归。
(1)求n! SMU1007(2)N!中有多少个0。
SMU1186。
方法1:直接搜索含5的因数个数。
方法2:找规律1~n中有5的因数在5、10、15、……中共有n/5个,提取5后是1、2、3、……n/5。
重复前面的方法即可。
(3)菲波那契数列SMU1017,POJ2753(4)回文数(5)SMU1082、1131、1142、1267、1281、1186、1608(棋盘之路)(6)SMP1134 王小二切饼、1133 下楼问题、1285 青蛙过河、1108 整数划分问题2.步骤类的递归(1)进制转换SMU1130或取各位数字SMU1023 各位数字的和(2)二分法SMU1153、1168、1185、1187、1598、1165(3)Hanoi塔问题SMU1224(4)逆波兰表达式POJ2694(5)八皇后问题POJ2754(6)木棍问题POJ2817(7)分书问题SMU1267(8)二叉树POJ2756(9)放苹果POJ1664(10)SMP 1287 Sine之舞(11)POJ2815、2749、2790、2787、2775、2802、2803、1191、1321POJ上的问题看《程序设计导引及在线实践》3.多方向的递归(这几个都比较有趣)(1)最大黑区域(四个方向)SMU外1221(2)SMU1550 n个数取出m个数的情况。
(3)滑雪POJ1088//动规(4)红与黑POJ2816(5)迷宫问题4.扩展的欧几里得算法(ax+by=c的求解)。
第九章 递归
1 2 3 A
B 0
C
3 A B C
2 A C B
3 A B C 1 A B C 2 A C B
6
0 6 6
3 A B C
0
A
B 6 0
C
2 A C B 3 A B C
29
main() 2 A C B 6 { int m; 3 A B C 0 printf("Input the number of disks scanf("%d",&m); printf("The steps to moving %3d hanoi(m,'A','B','C'); (0) } void hanoi(int n,char x,char y,char z) (1) { (2) if(n==1) (3) move(1,x,z); (4) else{ (5) hanoi(n-1,x,z,y); (6) move(n,x,z); (7) hanoi(n-1,y,x,z); (8) } (9) }
…}
…}
3
递归条件
(1)解决问题时,可以把一个问题转化为一 个新的问题,而这个新的问题的解决方法 仍与原问题的解法相同,只是所处理的对 象有所不同,这些被处理的对象之间是有 规律的递增或递减。 (2)可以通过转化过程使问题得到简化。 (3)必定要有一个明确的结束递归的条件, 否则递归将会无止境地进行下去,直到耗 尽系统资源。也就是说必须要某个终止递 归的条件。
19
递归到非递归的转换 ——直接转换法
25 36 72 18 99 49 54 63
void recfunc ( int A[ ], int n ) { if ( n >= 0 ) { printf(“%d A, n );//尾递归 } }
递归
计算机科学的新学生通常难以理解递归程序设计的概念。
递归思想之所以困难,原因在于它非常像是循环推理(circular reasoning)。
它也不是一个直观的过程;当我们指挥别人做事的时候,我们极少会递归地指挥他们。
对刚开始接触计算机编程的人而言,这里有递归的一个简单定义:当函数直接或者间接调用自己时,则发生了递归。
递归的经典示例计算阶乘是递归程序设计的一个经典示例。
计算某个数的阶乘就是用那个数去乘包括 1 在内的所有比它小的数。
例如,factorial(5) 等价于 5*4*3*2*1,而factorial(3) 等价于 3*2*1。
阶乘的一个有趣特性是,某个数的阶乘等于起始数(starting number)乘以比它小一的数的阶乘。
例如,factorial(5) 与 5 * factorial(4) 相同。
您很可能会像这样编写阶乘函数:清单 1. 阶乘函数的第一次尝试int factorial(int n){return n * factorial(n - 1);}不过,这个函数的问题是,它会永远运行下去,因为它没有终止的地方。
函数会连续不断地调用 factorial。
当计算到零时,没有条件来停止它,所以它会继续调用零和负数的阶乘。
因此,我们的函数需要一个条件,告诉它何时停止。
由于小于 1 的数的阶乘没有任何意义,所以我们在计算到数字 1 的时候停止,并返回 1 的阶乘(即 1)。
因此,真正的递归函数类似于:清单 2. 实际的递归函数int factorial(int n){if(n == 1){return 1;}else{return n * factorial(n - 1);}}可见,只要初始值大于零,这个函数就能够终止。
停止的位置称为基线条件(base case)。
基线条件是递归程序的最底层位置,在此位置时没有必要再进行操作,可以直接返回一个结果。
所有递归程序都必须至少拥有一个基线条件,而且必须确保它们最终会达到某个基线条件;否则,程序将永远运行下去,直到程序缺少内存或者栈空间。
递归(高级数据结构的基础)
递归(⾼级数据结构的基础)递归应该是初学者最难啃的⼀块⾻头,很多⼈也是半懂不懂,结果学到很深的境地也会因为⾃⼰基础不好,导致发展太慢。
因此我希望初学者还是深刻理解递归及深搜,这样以后再继续向前学。
递归,我们把这个字分为两个部分:递:所谓递即向下传递,换⼀种理解⽅式就是间接或直接地调⽤⾃⼰本⾝,且递归通常把⼀个⼤型复杂的问题层层转换成⼀个规模较⼩的⼦问题,所以递的意思便是把问题转变成⼀个个的字问题,然后逐步解决。
归:归也是初学者不明⽩的地⽅之⼀,难道解决完⼦问题就完了吗,不存在的,如果你想⽤⼦问题的值,那归的作⽤就体现出来了,我们可以先“递”,然后在每个最⼩的问题的解决的时候返回最⼩的问题的值,最后解决⽗问题。
但是要注意的是递归是有边界的,即最⼩的⼦问题便是他的边界。
如果递归有这么”简单“就好了,但是可能有些⼈对递归的理解也仅仅到这种程度,如果上⾯你还不明⽩下⾯有例⼦。
上⽂的递归仅仅是对所有递归的⼀个概括,但递归其实可以分为两⼤类:尾部递归:这个也是很好理解的,如果⼀个递归函数的尾部才是调⽤⾃⼰,那就是尾部递归,这个⽐较好理解⽐如下⾯这个例⼦:int jiecheng(int n){if(n==1)return1;elsereturn jiecheng(n-1)*n;}这个函数顾名思义求n的阶乘,我们先进⾏⼀波⼩⼩的数学推理,⼀个数的阶乘就是(这个数-1)的阶乘乘上这个数。
并因此可以继续向下求这个数-1的阶乘(这便是”递“),直到这个数到1时,便开始”归“,不断返回值,到最后返回这个数的阶乘。
emmm,这似乎很简单,但是还有⼀个⽐较难的递归。
中间递归:中间递归和平常的递归不⼀样的是在递归函数中,递归到下⼀层的语句后⾯还有语句,这时候在归上来的时候就不仅仅再返回值了,还会再执⾏上⼀层的语句,参数也执⾏上⼀次的参数,这种递归常常在搜索中被实现。
⽐如如果有这样的⼏个房间,每个房间都有两个门,有⼀个门都可以进⼊下⼀个房间,⽽另⼀个门则可以进⼊另⼀个房间,在这些房间⾥寻找宝⽯,如果找到就原路返回。
递归的总结
递归的总结什么是递归递归是一种解决问题的方法,它在函数的定义中调用函数自身。
通过将问题分解为更小的子问题并解决它们,递归可以解决复杂的问题。
递归在许多算法和数据结构中都有广泛的应用,特别是在树和图的操作中。
递归的基本原理递归的基本原理是将问题分解为更小的子问题并解决它们,直到达到最小的基本问题,然后将结果合并起来得到最终解决方案。
递归函数通常包括两个部分:基本情况和递归情况。
基本情况是指递归函数的退出条件,当满足某个条件时,递归停止。
递归情况是指递归函数调用自身解决子问题。
递归的优点使用递归可以简化代码的实现,将复杂的问题分解为更小的问题,使得代码更易于理解和维护。
递归还能够处理一些特殊的问题,例如树和图的遍历、排列组合等。
在这些情况下,递归是最自然的解决方法。
递归的缺点递归可能导致栈溢出,特别是在处理大规模问题时。
每次递归函数调用都会在调用栈中占用一些内存,当递归层级过深时,会超过系统栈的容量。
递归的效率通常比迭代低,尤其是对于一些简单的问题,使用递归可能会导致不必要的计算。
递归的应用场景递归广泛应用于许多算法和数据结构中。
以下是一些常见的应用场景:1.树的遍历:递归可以方便地实现树的深度优先遍历和广度优先遍历。
2.排列组合:递归可以生成所有可能的排列组合,例如生成一个数组的所有子集、一个字符串的所有排列等。
3.动态规划:递归可以将一个复杂的问题分解为更小的子问题,并使用动态规划技术进行优化。
4.回溯算法:递归可以方便地实现回溯算法,用于解决一些搜索问题,例如八皇后问题、求解数独等。
注意事项在使用递归时,需要注意以下几点:1.确定递归的退出条件,避免出现死循环。
2.确保每次递归调用都在向退出条件逼近,否则可能会导致递归层级过深。
3.尽量避免不必要的递归调用,以提高代码的效率。
4.在处理大规模问题时,考虑使用迭代或其他更高效的解决方法,避免栈溢出的风险。
总结递归是一种强大的问题解决方法,它通过将问题分解为更小的子问题来解决复杂的问题。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
15
递归设计步骤
(1)对原问题f(s)进行分析,假设出 合理的“较小问题” f(s’); (2)假设f(s’)是可解的,在此基础 上确定f(s)的解,即给出f(s)与f(s’) 的关系; (3)确定一个特定情况(f(1)或f(0)) 的解,由此作为递归出口。
16
递归到非递归的转换
求解递归问题有两种方法: 1、直接求值,不需要回溯的(单向递归和 尾递归):使用一些中间变量保存中间结 果;
void sterfunc ( int A[ ], int n ) { //消除了尾递归的非递归函数 while ( n >= 0 ) { printf( "value %d" , A[n] ); n--; } }
21
递归到非递归的转换 ——间接转换法
间接转换法一般的过程如下:
将初始状态s0进栈 while (栈不为空) { 退栈,将栈顶元素赋给s if (s是要找的结果) 返回 else { 寻找到s的相关状态s1 将s1进栈 } }
第九章 递归
[内容提要] 1、递归的概念及设计方法 2、递归与回朔法 3、递归应用实例 4、递归评价
递归的定义
若一个对象部分地包含它自己, 或用它自己给自己定义, 则称这个对 象是递归的;若一个过程直接地或间 接地调用自己, 则称这个过程是递归 的过程。
2
递归的定义
直接递归 fun_a() { … fun_a() … }
11
递归模型
例如,阶乘函数 递归出口
1, 当n 0 时 n! n (n 1)!, 当 n 1时
递归体
12
递归的执行过程
实际上,递归是把一个不能或不好 直接求解的“大问题”转化为一个或 几个“小问题”来解决,再把这些 “小问题”进一步分解成更小的“小 问题”来解决,如此分解,直至每一 个“小问题”都可以直接解决(此时 分解到递归出口)。
A
B
C
1 C A B 8 2 A C B 6 0 3 A B C
A
B 6 0
C
2 A C B 3 A B C
3 A B C
0
30
main() 3 A B C 0 { int m; printf("Input the number of disks scanf("%d",&m); printf("The steps to moving %3d hanoi(m,'A','B','C'); (0) } void hanoi(int n,char x,char y,char z) (1) { (2) if(n==1) (3) move(1,x,z); (4) else{ (5) hanoi(n-1,x,z,y); (6) move(n,x,z); (7) hanoi(n-1,y,x,z); (8) } (9) }
23
回朔法
回溯法在用来求问题的所有解时,要回 溯到根,且根结点的所有子树都已被搜索 遍才结束。而回溯法在用来求问题的任一 解时,只要搜索到问题的一个解就可以结 束。这种以深度优先的方式系统地搜索问 题的解的算法称为回溯法。
24
递归与回朔的区别——指导思想
回溯法是从问题的某一种可能出发, 搜索从这种情况出发所能达到的所有 可能,当这一条路走到“尽头”的时 候,再倒回出发点,从另一个可能出 发,继续搜索。
9
递归模型
递归模型反映一个递归问题的递归结构。 一般地,一个递归模型是由递归出口和递归 体两部分组成,前者确定递归到何时为止, 后者确定递归的方式。
10
递归模型
递归出口的一般格式为: f(s0)=m0;这里的s0与m0均为常量,有的递归问 题可能有几个递归出口。 递归体的一般格式为: f(s)=g(f(s1), f(s2),……, f(sn),c1, c2,……, cm) 这里的s是一个递归“大问题”,s1, s2,……, sn是递归“小问题”,c1, c2,……,cm是若干 个可以直接(用非递归方法)解决的问题,g是 一个非递归函数,反映了递归问题的结构。
4
适用递归技术的问题
以下三种情况常常用到递归方法。 – 定义是递归的 – 数据结构是递归的 – 问题的解法是递归的
5
定义是递归的
例如,阶乘函数 1, 当n 0 时 n! n (n 1)!, 当 n 1时 求解阶乘函数的递归算法 long Factorial ( long n ) { if ( n == 0 ) return 1; else return n * Factorial (n-1); }
22
回朔法
回溯法是一个既带有系统性又带有跳跃性的 的搜索算法。它在包含问题的所有解的解 空间树中,按照深度优先的策略,从根结 点出发搜索解空间树。算法搜索至解空间 树的任一结点时,总是先判断该结点是否 肯定不包含问题的解。如果肯定不包含, 则跳过对以该结点为根的子树的系统搜索, 逐层向其祖先结点回溯。否则,进入该子 树,继续按深度优先的策略进行搜索。
26
递归技术应用实例——汉诺塔问题
例如,汉诺塔(Tower of Hanoi)问题的解法: 如果 n = 1,则将这一个盘子直接从 A 柱移到 C 柱上。否则,执行以下三步: ① 用 C 柱做过渡,将 A 柱上的 (n-1) 个盘子移 到 B 柱上: ② 将 A 柱上最后一个盘子直接移到 C 柱上; ③ 用 A 柱做过渡,将 B 柱上的 (n-1) 个盘子移 到 C 柱上。
13
求解阶乘 n! 的过程
主程序 main : fact(4) 参数 4 递 归 调 用 参 数 传 递 计算 4*fact(3) 返回 24
参数 3
参数 2 参数 1 参数 0
计算 3*fact(2)
计算 2*fact(1) 计算 1*fact(0) 直接定值 = 1
返回 6
返回 2 返回 1 返回 1
1 2 3 A
B 0
C
3 A B C
2 A C B
3 A B C 1 A B C 2 A C B
6
0 6 6
3 A B C
0
A
B 6 0
C
2 A C B C B 6 { int m; 3 A B C 0 printf("Input the number of disks scanf("%d",&m); printf("The steps to moving %3d hanoi(m,'A','B','C'); (0) } void hanoi(int n,char x,char y,char z) (1) { (2) if(n==1) (3) move(1,x,z); (4) else{ (5) hanoi(n-1,x,z,y); (6) move(n,x,z); (7) hanoi(n-1,y,x,z); (8) } (9) }
结 果 返 回
回 归 求 值
14
递归设计
递归设计先要给出递归模型,再转换成对应 的C语言函数。从递归的执行过程看,要解决 f(s),不是直接求其解,而是转化为计算f(s’) 和一个常量c’,求解f(s’)的方法与环境和求解 f(s)的方法与环境是相似的,但f(s)是一个“大 问题”,而f(s’)是一个“较小问题”,尽管f(s’) 还未解决,但向解决目标靠近了一步,这就是一 个“量变”,如此到达递归出口,便发生了“质 变”,递归问题解决了。
18
递归到非递归的转换 ——直接转换法
long FibIter ( long n ) { if ( n <= 1 ) return n; long twoback=0, oneback = 1, Current; for ( int i = 2; i <= n; i++ ) { Current = twoback + oneback; twoback=oneback; oneback = Current; } return Current; }
19
递归到非递归的转换 ——直接转换法
25 36 72 18 99 49 54 63
void recfunc ( int A[ ], int n ) { if ( n >= 0 ) { printf(“%d ”,A[n]); n--; recfunc ( A, n );//尾递归 } }
20
递归到非递归的转换 ——直接转换法
A
B
C
2 B A C
3 A B C
8
0
1 B C A 6
2 B A C
3 A B C
8
0
A
B 8
C
2 B A C
3 A B C
0
31
main() 2 B A C 8 { int m; 3 A B C 0 printf("Input the number of disks scanf("%d",&m); printf("The steps to moving %3d hanoi(m,'A','B','C'); (0) } void hanoi(int n,char x,char y,char z) (1) { (2) if(n==1) (3) move(1,x,z); (4) else{ (5) hanoi(n-1,x,z,y); (6) move(n,x,z); (7) hanoi(n-1,y,x,z); (8) } (9) }
2、不能直接求值,需要回溯的:用栈来保 存中间结果。
17
递归到非递归的转换 ——直接转换法