04.递归算法讲解

合集下载

递归算法及经典例题详解

递归算法及经典例题详解

递归算法及经典例题详解
1.什么是递归
递归简单来说就是在运行过程中不断调用自己,直到碰到终止条件,返回结果的过程。

递归可以看作两个过程,分别是递和归。

递就是原问题把要计算的结果传给子问题;归则是子问题求出结果后,把结果层层返回原问题的过程。

下面设一个需要经过三次递归的问题,为大家详细看一下递归的过程:当然,现实中我们遇到递归问题是不会按照图中一样一步一步想下来,主要还是要掌握递归的思想,找到每个问题中的规律。

2.什么时候使用递归
递归算法无外乎就是以下三点:1.大问题可以拆分为若干小问题2.原问题与子问题除数据规模不同,求解思路完全相同3.存在递归终止条件
而在实际面对递归问题时,我们还需要考虑第四点:
当不满足终止条件时,要如何缩小函数值并让其进入
下一层循环中
3.递归的实际运用(阶层计算)
了解了大概的思路,现在就要开始实战了。

下面我们来看一道经典例题:
求N的阶层。

首先按照思路分析是否可以使用递归算法:
1.N!可以拆分为(N-1)!*N
2.(N-1)!与N!只有数字规模不同,求解思路相同
3.当N=1时,结果为1,递归终止
满足条件,可以递归:
publicstaticintFactorial(int num){if(num==1){return num;}return num*Factorial(num-1);}
而最后的return,便是第四步,缩小参数num的值,让递归进入下一层。

一般来说,第四步往往是最难的,需要弄清该如何缩
小范围,如何操作返回的数值,这一步只能通过不断
地练习提高了(当然如果你知道问题的数学规律也是
可以试出来的)。

《c语言递归算法》课件

《c语言递归算法》课件
《C语言递归算法》PPT 课件
C语言递归算法是一种强大的编程技巧,通过函数自身调用实现问题的解决。 本课件将介绍递归算法的概念、实现方式、应用场景、优缺点以及与循环的 区别,同时还会通过案例演示帮助理解。
什么是递归算法?
基本概念
递归是指函数直接或间接地调用自身的过程。
递归特点
递归算法需要有基准条件和递推关系,用于结 束递归和推进递归过程。
递归算法的实现方式
递归函数
通过函数自身调用实现递归,需要定义递归函数和 递归终止条件。
递归流程图
通过流程图展示递归算法的执行过程,帮助理解递 归逻辑。
递归算法的应用场景
1 数学计算
递归算法可以用于解决数学问题,如斐波那契数列、阶乘等。
2 数据结构
递归算法在树、图等数据结构的遍历和搜索中有广泛应用。
递归算法的优点和缺点
优点
• 简化问题复杂度 • 代码结构清晰
缺点
• 执行效率较低 • 内存占用较高
递归算法与循环的区别
1
循环
2
迭代操作
3
递归
函数自身调用
区别
递归更直观,但消耗资源较多;循环更 高效,但代码可读性差。
递归算法的注意事项
1 递归终止条件
保证递归过程能够结束,否则可能导致死循 环。
2 堆栈溢出
过深的递归调用可能导致堆栈溢出,需要注 意递归深度。
递归算法的案例演示
斐波那契数列
通过递归实现斐波那契数列的计算。
二叉树遍历
通过递归遍历二叉树的各种方式。

递归算法原理

递归算法原理

递归算法原理
递归是一种算法设计技巧,它的原理是通过将一个问题分解成一个或多个规模较小但类似于原问题的子问题来解决。

递归算法通过反复调用自身来解决这些子问题,直到子问题的规模足够小并可以直接解决为止。

递归算法的主要思想是将问题转化为更小的同类问题的子问题,并在每一次递归调用中将问题的规模减小。

递归算法需要定义一个基准情况,即问题的最小规模情况,当问题达到基准情况时,递归的调用将停止,得到最终的解。

当使用递归算法时,需要注意以下几点:
1. 递归的结束条件:为了避免无限递归,递归函数必须定义结束条件,即基准情况。

2. 递归调用:在函数内部调用自身来解决规模较小的子问题。

3. 子问题规模的减小:每次递归调用时,子问题的规模应该比原问题要小。

4. 递归栈:在每次递归调用时,系统会将当前的函数调用信息存储在递归栈中,当递归调用结束后,系统将会按照递归栈的顺序逐个弹出函数调用信息,直到返回最终的解。

递归算法在解决某些问题时非常有效,例如树和图的遍历、排列组合、分治算法等。

然而,递归算法也存在一些缺点,例如
递归调用会消耗较多的内存空间和时间复杂度较高等问题,因此在实际应用中需要根据具体情况来选择是否使用递归算法。

递归算法及经典递归例子代码实现

递归算法及经典递归例子代码实现

递归算法及经典递归例子代码实现递归算法是一种在函数体内调用函数本身的算法。

通过递归,问题可以被分解为规模更小的子问题,直到达到基本情况,然后将所有的子问题的解合并起来,得到原始问题的解。

递归算法的实现通常包含两个要素:基本情况和递归调用。

基本情况是指不能再进一步分解的情况,一般是针对问题的最小输入。

递归调用是指在解决子问题之后,将问题规模缩小,然后调用自身来解决更小规模的问题。

下面将介绍三个经典的递归例子,并给出相应的代码实现。

1.阶乘计算:阶乘是指从1到给定的数字n之间所有整数的乘积。

它是递归问题的经典例子之一```pythondef factorial(n):if n == 0:return 1else:return n * factorial(n - 1)```在阶乘的递归实现中,基本情况是n等于0时,返回1、递归调用是将问题规模变为n-1,然后将得到的结果与n相乘。

通过递归调用,可以一直计算到n为1,然后将每个阶乘结果逐步合并返回,最终得到n的阶乘。

2.斐波那契数列:斐波那契数列是指从0和1开始,后续的数字都是前两个数字之和。

```pythondef fib(n):if n <= 0:return 0elif n == 1:return 1else:return fib(n - 1) + fib(n - 2)```在斐波那契数列的递归实现中,基本情况是n小于等于0时返回0,n等于1时返回1、递归调用是将问题规模分为两个子问题,分别计算n-1和n-2的斐波那契数,然后将两个子问题的结果相加返回。

通过递归调用,可以一直计算到n为0或1,然后将每个斐波那契数逐步合并返回,最终得到第n个斐波那契数。

3.二叉树遍历:二叉树遍历是指按照一定的顺序访问二叉树的所有节点。

```pythonclass TreeNode:def __init__(self, val=0, left=None, right=None):self.val = valself.left = leftself.right = rightdef inorderTraversal(root):if root is None:return []else:return inorderTraversal(root.left) + [root.val] + inorderTraversal(root.right)```在二叉树的中序遍历的递归实现中,基本情况是判断当前节点是否为空,如果为空则返回一个空列表。

递归算法知识点总结

递归算法知识点总结

递归算法知识点总结一、基本概念递归算法的基本概念是基于递归函数的思想。

递归函数是一个调用自身的函数。

递归算法通常可以分为两种类型:简单递归和复杂递归。

简单递归是指在递归函数中直接调用自身,而复杂递归则是指在递归函数中可能会有多个递归调用。

递归算法通常用于解决可以分解为若干子问题的问题,这种方法通常可以更加简洁地解决问题,但同时也可能会带来一些计算复杂度的问题。

递归算法的设计通常包括以下几个步骤:1. 确定基本情况:在设计递归函数时,通常需要确定一个或多个基本情况。

基本情况通常是指在递归函数中不需要再次调用自身的情况。

2. 确定递归情况:在设计递归函数时,需要确定一个或多个递归情况。

递归情况通常是指在递归函数中需要调用自身的情况。

3. 确定递归方式:当确定了递归函数的基本情况和递归情况之后,就需要确定递归函数的调用方式。

通常有两种方式:直接递归和间接递归。

4. 编写递归函数:根据确定的基本情况、递归情况和递归方式,编写递归函数。

5. 测试递归函数:编写递归函数后,需要对递归函数进行测试,确保递归函数能够正确地解决问题。

二、递归算法的原理递归算法的原理是基于递归函数的调用。

当一个递归函数被调用时,它会将自身的执行环境保存到栈中,并且在栈中分配一些空间。

在递归函数中,如果有一些局部变量,这些变量会在栈中分配空间。

随着递归函数的深入调用,栈中的空间也会不断增加。

在递归函数的执行过程中,通常需要考虑递归栈的压栈和出栈操作。

在递归函数被调用时,会执行一些初始化操作,并将递归参数保存到栈中。

在递归函数中,如果遇到递归情况,会再次调用自身,并且将自身的执行环境保存到栈中。

在递归函数的执行过程中,如果遇到基本情况,就会结束当前递归调用,并且从栈中释放空间。

递归算法的原理是基于递归函数的深度调用的。

当递归函数被调用时,会执行一些初始化过程,并将递归参数保存到栈中。

当递归函数执行完毕后,会从栈中释放空间。

在递归函数的执行过程中,栈中的空间会不断增加和释放。

递归算法详解完整版

递归算法详解完整版

递归算法详解完整版递归算法是一种重要的算法思想,在问题解决中起到了很大的作用。

它通过将一个大问题划分为相同或类似的小问题,并将小问题的解合并起来从而得到大问题的解。

下面我们将详细介绍递归算法的定义、基本原理以及其应用。

首先,我们来定义递归算法。

递归算法是一种通过调用自身解决问题的算法。

它通常包括两个部分:基础案例和递归步骤。

基础案例是指问题可以被直接解决的边界情况,而递归步骤是指将大问题划分为较小问题并通过递归调用自身解决。

递归算法的基本原理是"自顶向下"的思维方式。

即从大问题出发,不断将问题划分为较小的子问题,并解决子问题,直到达到基础案例。

然后将子问题的解合并起来,得到原始问题的解。

递归算法的最大特点是简洁而优雅。

通过将复杂问题分解为简单问题的解决方式,可以大大减少代码的复杂程度,提高程序的效率和可读性。

但是递归算法也有一些缺点,包括递归深度的限制和复杂度的不确定性。

过深的递归调用可能导致栈溢出,而不合理的递归步骤可能导致复杂度过高。

递归算法有许多应用场景,我们来介绍其中一些典型的应用。

1.阶乘问题:计算一个数的阶乘。

递归算法可以通过将问题划分为更小的子问题来解决。

例如,n的阶乘可以定义为n乘以(n-1)的阶乘。

当n 等于1时,我们可以直接返回1作为基础案例。

代码如下:```int factorial(int n)if (n == 1)return 1;}return n * factorial(n - 1);```2.斐波那契数列问题:求斐波那契数列中第n个数的值。

斐波那契数列的定义是前两个数为1,然后从第三个数开始,每个数都是前两个数的和。

递归算法可以通过将问题划分为两个子问题来解决。

当n等于1或2时,直接返回1作为基础案例。

代码如下:```int fibonacci(int n)if (n == 1 , n == 2)return 1;}return fibonacci(n - 1) + fibonacci(n - 2);```3.二叉树问题:对于给定的二叉树,递归算法可以通过递归调用左子树和右子树的解来解决。

递归算法步骤

递归算法步骤

递归算法步骤
递归算法是一种通过自身调用来解决问题的算法。

其步骤可以简述为以下几点:
1. 定义递归函数:首先需要定义一个递归函数,该函数负责解决具体的问题。

函数的参数通常包括输入数据和递归所需的其他参数。

2. 设定递归终止条件:在递归函数中,需要设定一个终止条件,当满足这个条件时,递归将停止并返回结果。

这是确保递归不会无限循环的重要部分。

3. 处理基本情况:在递归函数中,需要考虑到一些基本情况,这些情况通常可以直接求解,而不需要继续进行递归。

在这些情况下,可以直接返回结果,从而减少递归的次数。

4. 缩小问题规模:在递归函数中,需要将原始问题划分成更小的子问题。

通过缩小问题规模,可以将原始问题转化为更简单的形式,并且递归地解决这些子问题。

5. 调用递归函数:在递归函数中,需要调用自身来解决子问题。

通过递归调用,可以反复地将问题分解为更小的子问题,直到达到终止条件为止。

6. 整合子问题的解:在递归函数中,需要将子问题的解整合起来,得到原始问题的解。

这通常涉及到对子问题的解进行合并、计算或其他操作。

7. 返回结果:最后,递归函数需要返回结果。

这个结果可
以是最终的解,也可以是在每次递归调用中得到的中间结果。

需要注意的是,在使用递归算法时,要确保递归能够正确地终止,并且要注意避免出现无限递归的情况。

另外,递归算法的效率通常较低,因此在设计算法时要考虑到时间和空间复杂度的问题。

递归算法详解完整版

递归算法详解完整版

递归算法详解标准化管理处编码[BBX968T-XBB8968-NNJ668-MM9N]递归冯文科一、递归的基本概念。

一个函数、概念或数学结构,如果在其定义或说明内部直接或间接地出现对其本身的引用,或者是为了描述问题的某一状态,必须要用至它的上一状态,而描述上一状态,又必须用到它的上一状态……这种用自己来定义自己的方法,称之为递归或递归定义。

在程序设计中,函数直接或间接调用自己,就被称为递归调用。

二、递归的最简单应用:通过各项关系及初值求数列的某一项。

在数学中,有这样一种数列,很难求出它的通项公式,但数列中各项间关系却很简a与前面临近几项之间的关单,于是人们想出另一种办法来描述这种数列:通过初值及n系。

要使用这样的描述方式,至少要提供两个信息:一是最前面几项的数值,一是数列间各项的关系。

比如阶乘数列1、2、6、24、120、720……如果用上面的方式来描述它,应该是:a的值,那么可以很容易地写成这样:如果需要写一个函数来求n这就是递归函数的最简单形式,从中可以明显看出递归函数都有的一个特点:先处理一些特殊情况——这也是递归函数的第一个出口,再处理递归关系——这形成递归函数的第二个出口。

递归函数的执行过程总是先通过递归关系不断地缩小问题的规模,直到简单到可以作为特殊情况处理而得出直接的结果,再通过递归关系逐层返回到原来的数据规模,最终得出问题的解。

以上面求阶乘数列的函数)f为例。

如在求)3(f时,由于3不是特殊值,因此需(n要计算)2(3f,但)2(f是对它自己的调用,于是再计算)2(f,2也不是特殊值,需要计*算)1(f,返回)1(= 2f,需要知道)1(f的值,再计算)1(f,1是特殊值,于是直接得出1*上一步,得23*)2()3(==f,从而得最终=f)1(32**)2(==f2f,再返回上一步,得6解。

用图解来说明,就是下面再看一个稍复杂点的例子。

【例1】数列}{n a 的前几项为1、111+、11111++、1111111+++、……输入n ,编程求n a 的精确分数解。

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

1.用递归法计算n!【讲解】递归是算法设计中的一种基本而重要的算法。

递归方法即通过函数或过程调用自身将问题转化为本质相同但规模较小的子问题,是分治策略的具体体现。

递归方法具有易于描述、证明简单等优点,在动态规划、贪心算法、回溯法等诸多算法中都有着极为广泛的应用,是许多复杂算法的基础。

递归概述一个函数在它的函数体内调用它自身称为递归(recursion)调用。

是一个过程或函数在其定义或说明中直接或间接调用自身的一种方法,通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。

递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。

递归的能力在于用有限的语句来定义对象的无限集合。

用递归思想写出的程序往往十分简洁易懂。

一般来说,递归需要有边界条件、递归前进段和递归返回段。

当边界条件不满足时,递归前进;当边界条件满足时,递归返回。

使用递归要注意以下几点:(1)递归就是在过程或函数里调用自身;(2)在使用递增归策略时,必须有一个明确的递归结束条件,称为递归出口。

例如有函数r如下:int r(int a){ b=r(a−1);return b;}这个函数是一个递归函数,但是运行该函数将无休止地调用其自身,这显然是不正确的。

为了防止递归调用无终止地进行,必须在函数内有终止递归调用的手段。

常用的办法是加条件判断,满足某种条件后就不再作递归调用,然后逐层返回。

构造递归方法的关键在于建立递归关系。

这里的递归关系可以是递归描述的,也可以是递推描述的。

例4-1 用递归法计算n!。

n!的计算是一个典型的递归问题。

使用递归方法来描述程序,十分简单且易于理解。

(1)描述递归关系递归关系是这样的一种关系。

设{U1,U2,U3,…,Un,…}是一个序列,如果从某一项k开始,Un和它之前的若干项之间存在一种只与n有关的关系,这便称为递归关系。

注意到,当n≥1时,n!=n*(n−1)!(n=0时,0!=1),这就是一种递归关系。

对于特定的k!,它只与k与(k−1)!有关。

(2)确定递归边界在步骤1的递归关系中,对大于k的Un 的求解将最终归结为对Uk的求解。

这里的Uk称为递归边界(或递归出口)。

在本例中,递归边界为k=0,即0!=1。

对于任意给定的N!,程序将最终求解到0!。

确定递归边界十分重要,如果没有确定递归边界,将导致程序无限递归而引起死循环。

例如以下程序:#include <stdio.h>int f(int x){ return(f(x−1));}main(){ printf(f(5));}它没有规定递归边界,运行时将无限循环,会导致错误。

(3)写出递归函数并译为代码将步骤1和步骤2中的递归关系与边界统一起来用数学语言来表示,即n!= n*(n−1)! 当n>1时n!= 1 当n=1时再将这种关系翻译为代码,即一个函数:long f(int n){ long g;if(n<0) printf("n<0,输入错误!");else if(n==1) g=1;else g=n*f(n−1);return(g);}(4)完善程序主要的递归函数已经完成,设计主程序调用递归函数即可。

#include <stdio.h>long f(int n){ long g;if(n<0) printf("n<0,输入错误!");else if(n==1) g=1;else g=n*f(n-1);return(g);}void main(){ int n;long y;printf(" 计算n!,请输入n: ");scanf("%d",&n);y=f(n);printf(" %d!=%ld \n",n,y);}程序中给出的函数f是一个递归函数。

主函数调用f后即进入函数f执行,如果n<0,n==0或n=1时都将结束函数的执行,否则就递归调用f函数自身。

由于每次递归调用的实参为n−1,即把n−1的值赋予形参n,最后当n−1的值为1时再作递归调用,形参n的值也为1,将使递归终止,然后可逐层退回。

下面我们再举例说明该过程。

设执行本程序时输入为5,即求 5!。

在主函数中的调用语句即为y=f(5),进入f函数后,由于n=5,不等于0或1,故应执行f=f(n−1)*n,即f=f(5−1)*5。

该语句对f作递归调用即f(4)。

进行4次递归调用后,f函数形参取得的值变为1,故不再继续递归调用而开始逐层返回主调函数。

f(1)的函数返回值为1,f(2)的返回值为1*2=2,f(3)的返回值为2*3=6,f(4) 的返回值为6*4=24,最后返回值f(5)为24*5=120。

综上,得出构造一个递归方法基本步骤,即描述递归关系、确定递归边界、写出递归函数并译为代码,最后将程序完善。

木桩A 木桩B 木桩C木桩A 木桩B木桩C 图1图35-1 汉诺塔游戏示意图(1)有三根桩子A 、B 、C 。

A 桩上有n 个碟子,最大的一个在底下,其余一个比一个小,依次叠上去。

(2)每次移动一块碟子,小的只能叠在大的上面。

(3)把所有碟子从A 桩全部移到C 桩上,如图35-1所示。

试求解n 个圆盘A 桩全部移到C 桩上的移动次数,并求出n 个圆盘的移动过程。

【讲解】1. 递归关系当n=1时,只一个盘,移动一次即完成。

当n=2时,由于条件是一次只能移动一个盘,且不允许大盘放在小盘上面,首先把小盘从A 桩移到B 桩;然后把大盘从A 桩移到C 桩;最后把小盘从B 桩移到C 桩,移动3次完成。

设移动n 个盘的汉诺塔需g(n)次完成。

分以下三个步骤:(1) 首先将n 个盘上面的n-1个盘子借助C 桩从A 桩移到B 桩上,需g(n-1)次;(2) 然后将A 桩上第n 个盘子移到C 桩上(1次);(3) 最后,将B 桩上的n-1个盘子借助A 桩移到C 桩上,需g(n-1)次。

因而有递归关系:g(n)=2*g(n-1)+1初始条件(递归出口):g(1)=1.2. 递归求解汉诺塔移动次数的程序设计// 汉诺塔n 盘移动次数#include<stdio.h>void main(){double g(int m); // 确定初始条件int n;printf(" 请输入盘片数n: ");scanf("%d",&n);if(n<=40)printf(" %d盘的移动次数为:%.0f\n",n,g(n));elseprintf(" %d盘的移动次数为:%.4e\n",n,g(n));}// 求移动次数的递归函数double g(int m){ double s;if(m==1)s=1;elses=2*p(m-1)+1;return s;}3. 程序运行示例请输入盘片数n: 4040盘的移动次数为:1099511627775请输入盘片数n: 6464盘的移动次数为:1.8447e+019这是一个很大的天文数字,若每一秒移动一次,那么需要数亿个世纪才能完成这64个盘的移动。

2.展示移动过程1. 求解思路设递归函数hn(n,a,b,c)展示把n个盘从A桩借助B桩移到C桩的过程,函数mv(a,c)输出从a桩到c桩的过程。

完成hn(n,a,b,c),当n=1时,即mv(a,c)。

当n>1时,分以下三步:(1) 将A桩上面的n-1个盘子借助C桩移到B桩上,即hn(n-1,a,c,b);(2) 将A桩上第n个盘子移到C桩上,即mv(a,c);(3) 将B桩上的n-1个盘子借助A桩移到C桩上,即hn(n-1,b,a,c)。

在主程序中,用hn(m,1,2,3)带实参m,1,2,3调用hn(n,a,b,c),这里m为具体移动盘子的个数。

同时设置变量k统计移动的次数。

2. 展示汉诺塔移动过程的程序设计函数mv(x,y)输出从x桩到y桩的过程,这里x,y分别不同情况取“A”或“B”或“C”,主函数调用hn(m,'A','B','C')。

// 展示汉诺塔移动过程的递归设计#include <stdio.h>int k=0;void mv(char x,char y) // 输出函数{ printf(" %c-->%c ",x,y);k++; // 统计移动次数if(k%5==0)printf("\n");}void hn(int m,char a,char b,char c) // 递归函数{ if(m==1) mv(a,c);else{ hn(m-1,a,c,b);mv(a,c);hn(m-1,b,a,c);}}#include <stdio.h>void main() // 主函数{ int n;printf("\n input n: "); scanf("%d",&n);hn(n,'A','B','C');printf("\n k=%d \n",k); // 输出移动次数}3. 程序运行示例与分析运行程序,input n: 4A-->B A-->C B-->C A-->B C-->AC-->B A-->B A-->C B-->C B-->AC-->A B-->C A-->B A-->C B-->Ck=15(1) 上面的运行结果是实现函数hn(4,A,B,C)的过程,可分解为以下三步:1)A-->B A-->C B-->C A-->B C-->A C-->B A-->B,这前7步是实施hn(3,A,C,B),即完成把上面3个盘从A桩借助C移到B桩。

2)A-->C 这1步是实施着mv(A,C),即把最下面的盘从A桩移到C桩。

3)B-->C B-->A C-->A B-->C A-->B A-->C B-->C,这后7步是实施hn(3,B,A,C),即完成把B桩的3个盘借助A移到C桩。

(2) 其中实现hn(3,A,C,B)的过程,可分解为以下三步:1) A-->B A-->C B-->C,这前3步是实施hn(2,A,B,C),即完成把上面两个盘从A桩借助B移到C桩。

相关文档
最新文档