递归算法详解完整版
递归算法及经典例题详解
递归算法及经典例题详解
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的值,让递归进入下一层。
一般来说,第四步往往是最难的,需要弄清该如何缩
小范围,如何操作返回的数值,这一步只能通过不断
地练习提高了(当然如果你知道问题的数学规律也是
可以试出来的)。
全排列递归算法详解
全排列递归算法详解
哎呀,啥是全排列递归算法呀?我一个小学生刚开始听到这个词的时候,简直是一头雾水,就好像走进了一个黑漆漆的迷宫,找不到出口。
咱们先来说说啥叫排列吧。
比如说,有三个数字1、2、3,把它们排排队,123 是一种排法,213 是一种排法,321 又是一种排法,这就是排列。
那全排列呢,就是把所有可能的排法都找出来。
那递归算法又是啥呢?这就像我们玩的搭积木。
我们先搭一层,然后在这一层的基础上再搭一层,一层一层地往上加。
递归算法也是这样,它自己调用自己,就像搭积木一样,一层一层地解决问题。
比如说,我们要对三个数字1、2、3 进行全排列。
那我们可以先固定第一个数字1,然后对剩下的2 和3 进行全排列。
怎么排呢?再固定2,剩下3 自己就是一种排法;然后固定3,剩下2 又是一种排法。
这是不是有点像我们做数学题的时候,一步一步来,每一步都为下一步打下基础?这不就和我们一层一层搭积木一样嘛!
再想想,如果数字更多了,比如1、2、3、4 这四个数字,那我们还是用同样的方法。
先固定一个数字,然后对剩下的数字进行全排列。
这是不是有点像我们解决难题的时候,把大问题分成一个个小问题,然后一个个解决?
哎呀,说了这么多,我感觉全排列递归算法就像是一个神奇的魔法,能把一堆乱七八糟的数字变得整整齐齐,让我们能找到所有的可能性。
反正我觉得全排列递归算法虽然有点难,但是只要我们认真去想,去琢磨,就一定能搞明白!。
递归算法详解完整版
递归算法详解完整版递归算法是一种重要的算法思想,在问题解决中起到了很大的作用。
它通过将一个大问题划分为相同或类似的小问题,并将小问题的解合并起来从而得到大问题的解。
下面我们将详细介绍递归算法的定义、基本原理以及其应用。
首先,我们来定义递归算法。
递归算法是一种通过调用自身解决问题的算法。
它通常包括两个部分:基础案例和递归步骤。
基础案例是指问题可以被直接解决的边界情况,而递归步骤是指将大问题划分为较小问题并通过递归调用自身解决。
递归算法的基本原理是"自顶向下"的思维方式。
即从大问题出发,不断将问题划分为较小的子问题,并解决子问题,直到达到基础案例。
然后将子问题的解合并起来,得到原始问题的解。
递归算法的最大特点是简洁而优雅。
通过将复杂问题分解为简单问题的解决方式,可以大大减少代码的复杂程度,提高程序的效率和可读性。
但是递归算法也有一些缺点,包括递归深度的限制和复杂度的不确定性。
过深的递归调用可能导致栈溢出,而不合理的递归步骤可能导致复杂度过高。
递归算法有许多应用场景,我们来介绍其中一些典型的应用。
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.二叉树问题:对于给定的二叉树,递归算法可以通过递归调用左子树和右子树的解来解决。
6种基本算法 递归
6种基本算法递归递归是一种重要的算法思想,它在计算机科学中得到广泛应用。
本文将介绍六种基本的递归算法,并对其原理和应用进行讲解。
一、递归的基本概念递归是指一个函数在其定义中调用自身的过程。
递归算法通过将一个大问题划分为一个或多个相同或相似的子问题,然后通过解决子问题来解决原始问题。
递归算法具有简洁、优雅以及可读性强的特点,但同时也需要注意递归的停止条件,以避免无限递归的发生。
二、阶乘算法阶乘算法是递归算法中最经典的例子之一。
它的定义如下:```n! = n * (n-1) * (n-2) * ... * 1```其中,n为一个非负整数。
阶乘算法可以通过递归的方式实现,即:```fact(n) = n * fact(n-1)```其中,停止条件为`n=0`时,返回1。
三、斐波那契数列算法斐波那契数列是一个无限序列,其定义如下:```F(0) = 0F(1) = 1F(n) = F(n-1) + F(n-2) (n>1)```斐波那契数列算法可以通过递归的方式实现,即:```fib(n) = fib(n-1) + fib(n-2)```其中,停止条件为`n=0`或`n=1`时,返回相应的值。
四、二分查找算法二分查找算法是一种高效的查找算法,它的基本原理是将已排序的数组分成两部分,然后判断目标值在哪一部分,并继续在该部分中进行查找,直到找到目标值或者查找范围为空。
二分查找算法可以通过递归的方式实现,即:```binarySearch(arr, target, start, end) = binarySearch(arr, target, start, mid-1) (target < arr[mid])= binarySearch(arr, target, mid+1, end) (target > arr[mid])= mid (target = arr[mid])```其中,`arr`为已排序的数组,`target`为目标值,`start`和`end`为查找范围的起始和结束位置。
递归算法详解
递归算法详解what:在计算机科学中是指⼀种通过重复将问题分解为同类的⼦问题⽽解决问题的⽅法when:发现问题可以分解为同类⼦问题且采⽤同样的⽅式去解决how:找到递归出⼝和递归体步骤:通过分析题⽬是否可以分解为若⼲重复⼦问题,判断是否可以采⽤递归算法进⾏解决。
确定采⽤递归算法之后,开始找递归出⼝和递归体,这是递归算法的核⼼部分,下⾯通过两个题⽬讲⼀下我的解题步骤。
P1:leetcode226 翻转⼆叉树简单题解答:题⽬很简单,翻转⼆叉树,然后查看输⼊输出,其实就是将每个节点的左右⼦树进⾏了交换。
这个时候⼦问题已经出现了,交换左右⼦树,按题⽬说法就是翻转左右⼦树,可以采⽤递归来做。
接下来需要找到递归出⼝和递归体,⼀般情况下先找递归体,因为确定递归的时候⼤概就已经确定了递归体,然后就是考虑边界问题,确定递归出⼝。
回到题⽬,根据题⽬的分析,可以确定递归体即为交换翻转当前节点的左右⼦树,当遍历⾄根节点⽆节点可翻转时即退出递归,即找到递归出⼝当前节点为null。
接下来代码实现:public class Solution{/** 226. 翻转⼆叉树简单题**/public TreeNode invertTree(TreeNode root) {this.invert(root);return root;}private void invert(TreeNode root){if (null == root) {return;}TreeNode left = root.left;TreeNode right = root.right;root.right = invertTree(left);root.left = invertTree(right);}}class TreeNode {int val;TreeNode left;TreeNode right;TreeNode() {}TreeNode(int val) {this.val = val;}TreeNode(int val, TreeNode left, TreeNode right) {this.val = val;this.left = left;this.right = right;}}P2:leetcode24,给定⼀个链表,两两交换其中相邻的节点,并返回交换后的链表。
python中的递归算法详解
python中的递归算法详解递归算法是一种在函数内部调用自身的方法。
在Python中,递归可以实现复杂的问题解决,它的实现遵循以下几个关键步骤。
第一,定义基本情况:递归算法在每一次调用中都需要判断是否达到了基本情况,即算法可以直接返回结果而不继续调用自身。
这个基本情况确保算法不会无限递归下去,而是能够找到问题的解。
第二,拆分问题:递归算法需要将大问题拆分成子问题,在每一次递归调用中解决子问题。
这样,较大的问题就会逐渐转化为较小的问题,直到最终达到基本情况。
第三,调用自身:递归算法通过调用自身来解决子问题。
在每次递归调用中,算法会传递不同的参数,以便在下一次调用中解决不同的子问题。
递归算法的关键在于它能够将复杂的问题转化为简单的子问题,并通过不断调用自身来解决这些子问题。
举个例子,考虑一个计算阶乘的递归算法:```pythondef factorial(n):# 基本情况if n == 0:return 1# 拆分问题subproblem = factorial(n - 1)# 调用自身result = n * subproblemreturn result```在这个例子中,递归算法使用了基本情况 `n == 0` 来停止递归。
当输入参数为0时,算法直接返回1,否则继续调用自身,解决 `n - 1` 的阶乘问题。
最终,算法将计算 `n` 的阶乘并返回结果。
需要注意的是,递归算法在处理大规模问题时可能会遇到效率问题。
递归算法会调用自身多次,每次调用都需要保存状态并分配栈空间,所以在某些情况下,循环迭代可能会更加高效。
因此,在使用递归算法时,需要谨慎考虑问题的规模和算法实现的复杂度,以充分利用递归的优势。
排列组合递归算法
排列组合递归算法是一种基于递归思想的算法,用于解决与排列和组合相关的问题。
下面是排列组合递归算法的详细介绍:
基本概念:
排列(Permutation):从n个不同元素中取出m(m ≤n)个不同元素按照一定的顺序排成一列,称为从n个元素中取出m个元素的一个排列,所有排列的个数记为P(n,m)。
组合(Combination):从n个不同元素中取出m(m ≤n)个不同元素按照一定的顺序排成一列,不考虑排列的顺序,称为从n个元素中取出m个元素的一个组合,所有组合的个数记为C(n,m)。
递归的基本思想:
递归算法的基本思想是将一个复杂的问题分解为若干个简单的问题,然后将这些简单问题的解组合起来得到原问题的解。
在排列组合问题中,可以将一个大问题分解为若干个小问题,例如:从n个元素中取出m个元素的排列/组合问题可以分解为从剩余元素中继续取下一个元素的问题。
递归公式:
排列的递归公式:P(n,m) = n * P(n-1,m-1) + P(n-1,m)
组合的递归公式:C(n,m) = P(n,m) / P(m,m) = (n * P(n-1,m-1) + P(n-1,m)) / P(m,m)
应用示例:
使用排列组合递归算法可以解决很多与排列和组合相关的问题,例如:给定一个数组,求数组中所有元素的排列/组合数、给定一个集合,求集合的所有子集等。
注意事项:
在使用递归算法时需要注意避免出现无限递归的情况,需要对递归终止条件进行正确的设置。
另外,由于递归算法会涉及到大量的重复计算,因此在处理大规模数据时可能会效率较低,可以考虑使用动态规划等优化方法来提高算法的效率。
汉诺塔递归算法及详解
汉诺塔递归算法及详解
汉诺塔(Tower of Hanoi)是一个经典的数学谜题和递归问题。
它由三个塔杆和一些不同大小的圆盘组成,开始时圆盘按从大到小的顺序叠放在一个塔杆上。
目标是将所有圆盘从起始塔杆移动到目标塔杆上,同时遵守以下规则:
1. 一次只能移动一个圆盘。
2. 任何时刻,大的圆盘不能放在小的圆盘上面。
递归算法是解决汉诺塔问题的常用方法。
其基本思想是将问题分解为较小规模的子问题,然后通过递归地解决子问题来解决原问题。
以下是汉诺塔递归算法的详解:
1. 如果只有一个圆盘需要移动,则直接将圆盘从起始塔杆移动到目标塔杆上。
2. 如果有多个圆盘需要移动,则按以下步骤进行操作:
- 将除最下方的圆盘以外的上方圆盘从起始塔杆移动到辅助塔杆上。
这可以通过递归调用解决较小规模的子问题来实现,即将上方圆盘从起始塔杆移动到目标塔杆上(目标塔杆作为新的辅助塔杆)。
- 然后将最下方的圆盘从起始塔杆直接移动到目标塔杆上。
- 最后,将辅助塔杆上的所有圆盘移动到目标塔杆上,这可以通过递归调用解决较小规模的子问题来实现,即将上方圆盘从辅助塔杆移动到起始塔杆上(起始塔杆作为新的目标塔杆)。
通过递归地应用以上步骤,就可以实现将所有圆盘从起始塔杆移动到目标塔杆上的操作。
递归求组合数
递归求组合数组合数是组合数学中的一种重要概念,用于表示从n个元素中选取k个元素的不同方式的数量。
在数学中,组合数C(n, k)可以通过递归方法求解。
本文将介绍递归算法的原理,并通过实例演示如何使用递归求解组合数。
1. 递归算法原理递归算法是一种通过反复调用自身来解决问题的方法。
在求解组合数时,递归算法可以通过以下步骤实现:步骤一:如果k等于0或者k等于n,直接返回1,表示选择的元素个数为0或者选择所有元素。
步骤二:否则,组合数C(n, k)等于C(n-1, k-1) + C(n-1, k)。
其中C(n-1, k-1)表示从n-1个元素中选取k-1个元素的方式数量,C(n-1, k)表示从n-1个元素中选取k个元素的方式数量。
步骤三:通过递归调用上述步骤二,即可求解组合数C(n, k)。
2. 递归求组合数的实例下面通过一个实例演示如何使用递归求解组合数。
假设有一个集合{1, 2, 3, 4, 5},我们需要从中选取3个元素的所有不同组合方式数量。
首先,我们需要定义一个递归函数来求解组合数:```pythondef combination(n, k):if k == 0 or k == n:return 1else:return combination(n-1, k-1) + combination(n-1, k)```然后,我们可以调用该函数来求解组合数:```pythonresult = combination(5, 3)print("组合数C(5, 3)的值为:", result)```通过运行上述代码,可以得到结果:```组合数C(5, 3)的值为: 10```因此,从集合{1, 2, 3, 4, 5}中选取3个元素的不同组合方式数量为10。
3. 小结递归求组合数是一种简单而有效的方法,可以用于求解从n个元素中选取k个元素的不同方式的数量。
通过定义递归函数,反复调用自身,我们可以实现组合数的递归求解。
递归算法详解范文
递归算法详解范文递归是一种常用的算法思想,它通常用于解决可以被划分为更小规模相同问题的情况。
在递归算法中,问题被分解成更小的子问题,逐步求解子问题,最终得到整个问题的解。
接下来,我将详细介绍递归算法的原理、特点和应用。
一、递归算法的原理递归算法的原理是基于函数调用的特性。
在递归算法中,函数可以调用其自身来解决更小规模的子问题。
每次递归调用会将问题分解为更小规模的子问题,直到达到边界条件,然后逐步返回结果,最终得到整个问题的解。
递归算法通常具有以下两个重要的特点:1.递归定义:递归算法通过将问题分解为更小规模的子问题来定义。
2.递归调用:递归算法通过调用自身来解决更小规模的子问题。
递归算法的实现通常包含两个部分:基本情况和递归情况。
1.基本情况:基本情况是递归算法的边界条件,它表示问题已经足够小,可以直接求解,无需继续递归调用。
2.递归情况:递归情况是递归算法的重点,它描述了如何将当前问题分解为更小规模的子问题,并调用自身来解决子问题。
递归算法在实现时需要注意以下几点:1.基本情况的设置要合理,以确保算法能够终止。
2.递归调用时,问题规模要比上一次递归调用减小,确保算法能够在有限步骤内得到解。
3.递归算法的效率通常比较低,因为它会重复计算一些子问题。
可以通过记忆化、动态规划等方法进行优化。
二、递归算法的特点递归算法具有以下几个特点:1.逻辑简单清晰:递归算法的实现通常比较简洁,容易理解和调试。
2.代码复用性好:递归算法可以将问题分解为更小规模的子问题,这样可以复用代码来解决不同规模的问题。
3.可读性强:递归算法通常可以直观地反映问题的结构和解题思路。
4.可扩展性好:递归算法可以方便地将问题扩展到更大规模。
然而,递归算法也存在一些局限性:1.递归算法通常会消耗较多的内存空间,因为每一次递归调用都需要保存一些中间结果。
2.递归算法的效率较低,因为它会存在重复计算的问题,可以通过优化方法进行提升。
3.递归算法可能会因为递归过深而导致栈溢出,需要注意递归调用的次数。
离散数学中递归算法的工作原理解析
离散数学中递归算法的工作原理解析离散数学是一门研究离散对象和离散结构的数学学科,其在计算机科学中有着广泛的应用。
递归算法是离散数学中的一个重要概念,本文将对递归算法的工作原理进行解析。
1. 递归算法的定义递归算法是一种通过反复调用自身来解决问题的算法。
它通常包含了一个递归出口(基本情况)和一个递归体(递归情况)。
当问题达到递归出口时,算法停止递归并返回结果。
否则,算法继续递归调用自身,将问题分解为规模更小的子问题,并在子问题上进行递归求解。
2. 递归算法的优点与注意事项递归算法具有以下优点:1) 逻辑清晰简洁:递归算法能够使用简洁的方式描述问题的解决过程。
2) 结构灵活:递归算法能够解决各种类型的问题,适用范围广泛。
然而,递归算法也需要注意以下事项:1) 递归深度:递归算法的性能与问题的规模成反比。
递归深度过大可能导致栈溢出或性能下降。
2) 重复计算:递归算法中可能存在重复计算,增加了计算量。
可以使用记忆化技术(如动态规划)来优化递归算法。
3. 递归算法的应用场景递归算法在计算机科学中有广泛的应用,包括但不限于以下领域:1) 数据结构:递归算法常用于处理树、图、链表等数据结构,如树的遍历、图的深度优先搜索等。
2) 排列组合:递归算法可以用于生成排列组合,如全排列、组合数等。
3) 分治算法:分治算法通常使用递归来将问题分解为更小的子问题,并分别求解。
4. 递归算法的实现步骤实现一个递归算法通常包括以下步骤:1) 定义递归出口:确定递归算法何时停止递归,返回结果。
2) 确定递归体:根据问题的特点,将问题分解为规模更小的子问题,并调用自身来解决子问题。
3) 设计递归调用:根据子问题的规模和性质,设计递归调用的方式。
4) 处理子问题的结果:将子问题的结果合并得到原问题的结果。
5. 递归算法的示例:阶乘计算下面通过计算阶乘的例子来具体说明递归算法的工作原理:```python# 递归算法计算阶乘def factorial(n):if n == 0:return 1else:return n * factorial(n-1)```上述代码中,factorial函数通过递归来计算阶乘。
汉诺塔递归算法及详解
汉诺塔递归算法及详解汉诺塔(Hanoi Tower)是一种数学谜题,由法国数学家Édouard Lucas在19世纪中期提出。
这个谜题由三根柱子和一组圆盘组成,圆盘从上到下按照从小到大的顺序放置在柱子上。
问题的目标是将所有圆盘从一个柱子移动到另一个柱子,每次只能移动一个圆盘,并且不能将大的圆盘放在小的圆盘上面。
解决汉诺塔问题的一种常见方法是使用递归算法。
递归是一种数学和计算机科学中常见的方法,通过将复杂的问题分解为更小的相同问题的子问题来解决。
汉诺塔的递归算法主要包含以下步骤:1.将N-1个圆盘从起始柱子移动到中间柱子上,这可以通过将起始柱子作为源柱子,中间柱子作为辅助柱子,目标柱子为空柱子来实现。
这个步骤可以通过递归调用来实现,将起始柱子作为源柱子,中间柱子作为辅助柱子,目标柱子作为空柱子。
2.将第N个圆盘从起始柱子移动到目标柱子上。
3.将N-1个圆盘从中间柱子移动到目标柱子上,这可以通过将中间柱子作为源柱子,目标柱子作为辅助柱子,起始柱子作为空柱子来实现。
这个步骤可以通过递归调用来实现,将中间柱子作为源柱子,目标柱子作为辅助柱子,起始柱子作为空柱子。
下面是一个示例代码,使用递归算法解决汉诺塔问题:```pythondef hanoi(n, source, target, auxiliary):if n > 0:#将N-1个圆盘从起始柱子移动到中间柱子hanoi(n-1, source, auxiliary, target)#将第N个圆盘从起始柱子移动到目标柱子print("Move disk", n, "from", source, "to", target)#将N-1个圆盘从中间柱子移动到目标柱子hanoi(n-1, auxiliary, target, source)#测试n=3hanoi(n, 'A', 'C', 'B')```上述代码中,`hanoi(`函数接受四个参数:圆盘的数量n,起始柱子source,目标柱子target和辅助柱子auxiliary。
递归及递归算法分析课件
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 递推方程的特征方程求解
计算机算法基础(第3递归算法)
递归算法的缺点
性能问题
对于大规模数据,递归可能会导致大量的函 数调用和返回,这可能导致性能问题。
代码重复
在某些情况下,递归可能会导致代码重复, 这可能会降低代码的效率。
栈溢出
如果递归深度过大,可能会导致栈溢出。
调试困难
对于复杂的递归逻辑,调试可能会变得困难。
递归算法的应用场景
排序算法
如快速排序、归并排序等。
计算机算法基础(第3递归算 法)
• 递归算法概述 • 递归算法的优缺点 • 递归算法的实现 • 常见递归算法示例 • 递归算法的效率分析 • 总结与展望
01
递归算法概述
递归的定义
递归是指在函数或算法中直接或间接 调用自身的一种方法。
递归函数必须有一个或多个基准情况 ,当函数调用自身时,基准情况将被 执行,从而终止递归。
递归的基本思想
将问题分解为更小的子问题,递归地解决这些子问题,然后 将子问题的解组合起来得到原问题的解。
通过将问题规模逐渐减小,递归地逼近基准情况,最终达到 解决问题的目的。
递归算法的分类
直接递归
函数直接调用自身。
间接递归
函数通过调用其他函数间接地 调用自身。
多层递归
一个函数调用另一个函数,而 被调用的函数又调用自身。
如何优化递归算法
减少递归深度
通过将问题分解为更小的子问题,可以 减少递归调用的次数,从而降低空间复
杂度。
优化递归终止条件
合理设置递归终止条件,可以减少不 必要的递归调用,提高算法效率。
使用记忆化技术
在递归过程中,可以将已经计算过的 结果存储起来,避免重复计算,提高 算法效率。
使用迭代替代递归
在某些情况下,使用迭代算法替代递 归算法可以更有效地解决问题,降低 空间和时间复杂度。
递归算法详解
递归算法详解递归详解通过运⾏时堆栈来⽀持递归的调⽤,在我们刚接触递归的时候,国内很多教材都采⽤求阶乘和菲波那契数列来描述该思想,就如同深受⼤家敬爱的国产的C语⾔程序设计,⽼谭也⽤了阶乘来描述递归,以⾄于很多新⼿⼀看见阶乘就理所当然的认为是递归,坑了不少⼈,说实在的,描述这个思想还是可以,但是利⽤递归求阶乘可是没有⼀点好处,递归解决菲波那契数列效率更是低得惊⼈,这点是显⽽易见的!废话不多说,接下来我们进⼊正题!(不过说实话,我很讨厌接下来这些太理论的东西,说到底就是那么个意思,⼤家懂就好了,也可以当看看故事!我主要说的就是各种各样递归的实例)1:递归算法的思想递归算法是把问题转化为规模缩⼩了的同类问题的⼦问题。
然后函数(或过程)来表⽰问题的解。
在C语⾔中的运⾏堆栈为他的存在提供了很好的⽀持,过程⼀般是通过函数或⼦过程来实现。
递归算法:在函数或⼦过程的内部,直接或者间接地调⽤⾃⼰的算法。
2:递归算法的特点:递归算法是⼀种直接或者间接地调⽤⾃⾝算法的过程。
在计算机编写程序中,递归算法对解决⼀⼤类问题是⼗分有效的,它往往使算法的描述简洁⽽且易于理解。
递归算法解决问题的特点:(1) 递归就是在过程或函数⾥调⽤⾃⾝。
(2) 在使⽤递归策略时,必须有⼀个明确的递归结束条件,称为递归出⼝。
(3) 递归算法解题通常显得很简洁,但递归算法解题的运⾏效率较低。
所以⼀般不提倡⽤递归算法设计程序。
(4) 在的过程当中系统为每⼀层的返回点、局部量等开辟了栈来存储。
递归次数过多容易造成等。
所以⼀般不提倡⽤递归算法设计程序。
3:递归算法的要求递归算法所体现的“重复”⼀般有三个要求:⼀是每次调⽤在规模上都有所缩⼩(通常是减半);⼆是相邻两次重复之间有紧密的联系,前⼀次要为后⼀次做准备(通常前⼀次的输出就作为后⼀次的输⼊);三是在问题的规模极⼩时必须⽤直接给出解答⽽不再进⾏,因⽽每次递归调⽤都是有条件的(以规模未达到直接解答的⼤⼩为条件),⽆条件递归调⽤将会成为死循环⽽不能正常结束。
C语言的递归算法解析
C语言的递归算法解析递归算法是一种经常在编程中使用的重要技术。
在C语言中,递归算法可以通过函数的自我调用来实现。
本文将对C语言中的递归算法进行详细解析,并介绍递归算法在实际应用中的一些常见场景。
一、什么是递归算法递归算法是一种通过函数的自我调用来解决问题的方法。
在递归算法中,一个函数可以直接或间接地调用自身。
递归算法通常分为两个部分:基本情况和递归情况。
基本情况是指能够直接解决的问题,而递归情况是指将问题划分为子问题并通过递归调用解决。
递归算法的核心思想是将原问题转化为规模更小的子问题,并通过递归调用解决子问题。
递归算法必须要有一个终止条件,否则会进入无限循环,导致程序崩溃或者运行时间过长。
二、递归算法的实现在C语言中,递归算法可以通过函数的自我调用来实现。
下面是一个求解斐波那契数列的递归算法示例:```c#include <stdio.h>int fibonacci(int n) {if (n == 0 || n == 1) {return n;} else {return fibonacci(n-1) + fibonacci(n-2);}}int main() {int n = 10;int result = fibonacci(n);printf("The %dth Fibonacci number is: %d\n", n, result);return 0;}```在上述代码中,`fibonacci`函数通过递归调用自身来求解斐波那契数列的第n个数。
如果n为0或者1,那么直接返回n,否则将问题划分为求解第n-1个数和第n-2个数的和,并通过递归调用来解决子问题。
三、递归算法的优缺点递归算法具有以下优点:1. 递归算法可以简化问题的解决过程,将大问题划分为小问题,降低了问题解决的复杂度。
2. 递归算法的逻辑清晰,代码简洁,易于理解和维护。
然而,递归算法也存在一些缺点:1. 递归算法需要占用大量的系统栈空间,函数的调用层级过深可能导致栈溢出的问题。
递归及递归算法图解
递归问题的提出
第一步:将问题简化。 – 假设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)两种不同的递归函数?
递归和递推:比较下面两个示例
递归算法详解
递归算法详解Document number:WTWYT-WYWY-BTGTT-YTTYU-2018GT递归冯文科一、递归的基本概念。
一个函数、概念或数学结构,如果在其定义或说明内部直接或间接地出现对其本身的引用,或者是为了描述问题的某一状态,必须要用至它的上一状态,而描述上一状态,又必须用到它的上一状态……这种用自己来定义自己的方法,称之为递归或递归定义。
在程序设计中,函数直接或间接调用自己,就被称为递归调用。
二、递归的最简单应用:通过各项关系及初值求数列的某一项。
在数学中,有这样一种数列,很难求出它的通项公式,但数列中各项间关系却很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是特殊值,于是直接得出*2)2(==ff,再返回上一步,得)1(*1)1(=f,返回上一步,得2)3(==f,从而得最终解。
3=f)2(6*23*用图解来说明,就是Array下面再看一个稍复杂点的例子。
【例1】数列}{n a 的前几项为1、111+、11111++、1111111+++、……输入n ,编程求n a 的精确分数解。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
递归算法详解标准化管理处编码[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 的精确分数解。
分析:这个题目较易,发现11=a ,其它情况下有111-+=n n a a 。
如要求实数解的话,这基本已经可以写出递归函数了。
但由于题目要求精确的分数解,还需做一些调整。
设p q a n =-1,则由递归关系,有qp p p qa a n n +=+=+=-11111,再约分化简,即得n a 。
但发现一个问题:求出1-n a 时,需要返回两个整数:分子q 与分母p ,而通常的函数只能返回一个整数。
这个问题一般有两类解决办法,一种是让求值函数返回一个结构体变量,这样就可以返回两个变量了(其实还可以不只两个呢);另一种是在求值函数的参数表中加入两个指针变量或引用变量,通过参数给带回数值。
但由于后一种做法会使程序结构不清晰——返回值是由参数表得到的,因此我们使用前一种方法。
另外,在通过p q a n =-1得出qp p a n +=后,n a 就已经是最简分数了,无须化简。
证明如下: 若pq 是最简分数,即说明q p ,的最大公约数为1,即对任何q r ≤<1,都有r q mod 与r p mod 不全为0,不防记a r q =mod 、b r p =mod ,则有只要a 与b 不全为0,且r b r a <<,,就有a 与r b a mod )(+不全为0。
因此对任何的q r ≤<1,有r p mod 与r q p mod )(+不全为0。
而对于p r q ≤<的情况而言,记a r p =mod ,则有由于r q r a <<<≤0,0,因此同样有r p mod 与r q p mod )(+不全为0。
所以对任意p r ≤<1,都有r p mod 与r q p mod )(+不全为0,因此它们的最大公约数为1,即qp p +是最简分数。
虽然这是个要求1-n a (即p q )是最简分数的结论,但由于数列第二项为21,是最简分数,因此可以证明第三项也是最简分数,同时也证明对所有的n a ,求出的qp p +就是最简分数,无须化简。
具体代码如下:)90(≤≤N N N -0i 1+i 2+i N N N i N i 12+i )1(2+i , MAX*sizeof(char)); t[n]='\0';for(i=0;i<n;i++){t[q[i]]='Q';cout<<t<<endl;t[q[i]]='.';}cout<<endl;}bool test(int i, int k){int j;j=0;while(j<k && abs(j-k)!=abs(q[j]-i)) j++;if(j==k && mark[i]==false)return true;elsereturn false;}void search(int k){if(k==n){write();c++;return;}int i;for(i=0;i<n;i++)if(test(i, k)){mark[i]=true;q[k]=i;search(k+1);mark[i]=false; }}int main()六、练习【练习】为给定的表达式建立表达式树,并求值。
给定的表达式中,所有数字都是1位正整数,出现的符号可能为+、-、*、/、(、)。
分析:这是一个与一般数据结构书上讲的用栈计算的方法本质不同的方法。
在详细说明这个算法之前,需要首先明确这个算法用到的概念1、单元:一个单元可能是用括号括起来的一个表达式,或是一个整数;2、项:一个项是指由*与/连接起来的若干单元;3、表达式:一个表达式是指由+或-连接起来的若干项。
要建立表达式树,需要三个函数互相调用的函数:一个是getunit,用于建立一个单元;一个是getexpr,用于建立一个项,另一个就是build,用于建立一个表达式。
getunit函数较易,如果字符串首字母是(的话,那么从它后面的字符开始用build建立一个表达式,这个表达式就是一个单元;否则,就处理一个整数;getexpr函数是建立在getunit之上的,它先用getunit建立一个单元,然后不停地考察之后地连接符号是不是*或/,若是,则不停地重复读连接符、建立另一个单元、建立连接的操作,直到连接符号不是*或/为止。
build函数是用于最终建立表达式的,它先用getexpr建立一个项,再用符号将剩余的各项连接成二叉树。
代码如下:if(n>0){ hanoi(n-1,x,z,y); hanoi(n-1,y,x,z); }.w[10]中int knap(int s,int n){ 算法32是求n个数的和的递归算法。
算法33是相应的迭代版本。
假设n个数已存储在数组a的分量a[1],…,a[n]中。
float sum(int n){.a[n]都置为0a[0]=1; *//* ---------------------------------------- */void main(){int i;for ( i = 0; i < 5; i++ )printf("%d! = %d\n",i,factorial(i));/*递归阶乘函数调用*/ }/* ======================================== *//* 使用列印数组函数来说明递归调用 *//* ======================================== */int list[6] = { 1, 2, 3, 4, 5, 6 }; /* 数组内容 *//* ---------------------------------------- *//* 递归数组反向列印函数 *//* ---------------------------------------- */void invert_array(int j){if ( j < 6 ) /* 终止条件 */{/* 递归链表列印函数调用 */invert_array(j + 1);printf("[%d]",list[j]); /* 列印元素资料 */ }}/* ---------------------------------------- */ /* 主程式: 反向列印数组内容. *//* ---------------------------------------- */ void main(){int i;printf("数组的内容:\n");for ( i = 0; i < 6; i++ )printf("[%d]",list[i]); /* 列印元素资料 */ printf("\n"); /* 换行 */printf("递归列印数组的内容:\n");invert_array(0); /* 调用列印函数 */printf("\n"); /* 换行 */}/* ======================================== */ /* 递归阶乘函数来说明递归内部处理 *//* ======================================== *//* ---------------------------------------- */ /* 递归阶乘函数 *//* ---------------------------------------- */int factrial(int j){int sum = 0; /* 阶乘总和变数 */int temp = 0; /* 阶乘总和暂存变数 */if ( j == 0 ) /* 终止条件 */{sum = 1;printf("到达终止条件(j = 0)\n");}else{printf("从函数factrial(%d)调用前的状态: sum = %d\n", j, sum);temp = factrial(j - 1); /* 递归阶乘函数调用 */printf("返回函数factrial(%d)后的状态: sum = %d\n",j, sum);sum = j * temp; /* 计算j!的值 */printf(" ==> 在计算%d!阶乘后的状态: sum = %d\n",j, sum);}return sum;}/* ---------------------------------------- *//* 主程式: 计算整数 4 的阶乘. *//* ---------------------------------------- */void main(){printf("4! = %d\n",factrial(4)); /* 递归阶乘函数调用 */}/* ======================================== */ /* 递归的链表建立和列印 *//* ======================================== */ #include <>struct list /* 链表结构宣告 */ {int data; /* 节点资料 */struct list *next; /* 指向下一节点 */ };typedef struct list node; /* 定义新型态 */ typedef node *link; /* 定义新型态指标 */ /* ---------------------------------------- *//* 递归链表列印函数 *//* ---------------------------------------- */ void print_list(link ptr){if ( ptr != NULL ) /* 终止条件 */{printf("[%d]",ptr->data); /* 列印节点资料 */ /* 递归链表列印函数调用 */print_list(ptr->next);}}/* ---------------------------------------- */ /* 递归链表建立函数 *//* ---------------------------------------- */ link create_list(int *array,int len,int pos) {link head; /* 链表节点的指标 */if ( pos == len ) /* 终止条件 */return NULL;else{/* 建立节点记忆体 */head = ( link ) malloc(sizeof(node));if ( !head )return NULL;head->data = array[pos]; /* 建立节点内容 */head->next = create_list(array,len,pos + 1);return head;}}/* ---------------------------------------- *//* 主程式: 建立链表后将内容印出. *//* ---------------------------------------- */void main(){int list[6] = { 1, 2, 3, 4, 5, 6 }; /* 数组内容 */ link head; /* 指向链表开始 */head = create_list(list,6,0); /* 建立链表 */if ( !head ){printf("记忆体配置失败! \n");exit(1);}printf("链表的内容:\n");print_list(head); /* 列印链表 */printf("\n"); /* 换行 */}/* ======================================== */ /* 递归的链表建立和反向列印 *//* ======================================== */ #include <>struct list /* 链表结构宣告 */ {int data; /* 节点资料 */struct list *next; /* 指向下一节点 */ };typedef struct list node; /* 定义新型态 */ typedef node *link; /* 定义新型态指标 *//* ---------------------------------------- *//* 递归链表反向列印函数 *//* ---------------------------------------- */ void print_list(link ptr){if ( ptr != NULL ) /* 终止条件 */{/* 递归链表列印函数调用 */print_list(ptr->next);printf("[%d]",ptr->data); /* 列印节点资料 */ }}/* ---------------------------------------- *//* 递归链表建立函数 *//* ---------------------------------------- */ link create_list(int *array,int len,int pos){link head; /* 链表节点的指标 */if ( pos == len ) /* 终止条件 */return NULL;else{/* 建立节点记忆体 */head = ( link ) malloc(sizeof(node));if ( !head )return NULL;head->data = array[pos]; /* 建立节点内容 */ head->next = create_list(array,len,pos + 1); return head;}}/* ---------------------------------------- */ /* 主程式: 建立链表后将内容印出. *//* ---------------------------------------- */ void main(){int list[6] = { 1, 2, 3, 4, 5, 6 }; /* 数组内容 */ link head; /* 指向链表开始 */head = create_list(list,6,0); /* 建立链表 */if ( !head ){printf("记忆体配置失败! \n");exit(1);}printf("链表的内容:\n");print_list(head); /* 列印链表 */printf("\n"); /* 换行 *//* ======================================== *//* 河诺塔问题 *//* ======================================== *//* ---------------------------------------- *//* 河内塔问题的递归函数 *//* ---------------------------------------- */int hanoi(int dishs,int peg1,int peg2,int peg3) {if ( dishs == 1) /* 终止条件 */printf("盘子从 %d 移到 %d\n",peg1,peg3);else{hanoi(dishs - 1,peg1,peg3,peg2); /* 第一步骤 */ printf("盘子从 %d 移到 %d\n",peg1,peg3);hanoi(dishs - 1,peg2,peg1,peg3); /* 第三步骤 */ }}/* ---------------------------------------- */ /* 主程式: 找出河内塔问题的解. *//* ---------------------------------------- */ void main(){hanoi(3,1,2,3); /* 调用递归函数 */}/* ======================================== */ /* 应用递归来走迷宫 *//* 数字 0: 表示是可走的路 *//* 数字 1: 表示是墙壁,不可走的路 *//* 数字 2: 标示是走过的路 *//* ======================================== */int maze[7][10] = { /* 迷宫的数组 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,1, 0, 1, 0, 1, 0, 0, 0, 0, 1,1, 0, 1, 0, 1, 0, 1, 1, 0, 1,1, 0, 1, 0, 1, 1, 1, 0, 0, 1,1, 0, 1, 0, 0, 0, 0, 0, 1, 1,1, 0, 0, 0, 1, 1, 1, 0, 0, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };/* ---------------------------------------- */ /* 走迷宫的递归函数 *//* ---------------------------------------- */ int find_path(int x,int y){if ( x == 1 && y == 1 ) /* 是否是迷宫出口 */{maze[x][y] = 2; /* 记录最后走过的路 */return 1;}elseif ( maze[x][y] == 0 ) /* 是不是可以走 */{maze[x][y] = 2; /* 记录己经走过的路 */if ( ( find_path(x - 1,y) + /* 调用递归函数往上 */ find_path(x + 1,y) + /* 往下 */find_path(x,y - 1) + /* 往左 */find_path(x,y + 1) ) > 0 ) /* 往右 */return 1;else{maze[x][y] = 0; /* 此路不通取消记号 */ return 0;}}elsereturn 0;}/* ---------------------------------------- */ /* 主程式: 用递归的方法在数组迷宫找出口. *//* ---------------------------------------- */ void main(){int i,j;find_path(5,8); /* 调用递归函数 */printf("迷宫的路径如下图所示:\n");for ( i = 1; i < 6; i++) /* 印出迷宫的图形 */ {for ( j = 1; j < 9; j++)printf("%d",maze[i][j]); /* 印出各座标 */ printf("\n"); /* 换行 */}}/* ======================================== *//* 应用递归来解 N 皇后问题 *//* 数字 1: 表示是放置皇后 *//* 数字 0: 表示没有放置 *//* ======================================== */#define MAXQUEEN 8 /* 最大放置的皇后数 */int pad[MAXQUEEN][MAXQUEEN] = { /* N X N 的棋盘 */ 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0 };/* ---------------------------------------- *//* 放 N 个皇后的递归函数 *//* ---------------------------------------- */int put_queen(int x,int y,int times){int i,j,result = 0;if ( times > MAXQUEEN ) /* 终止条件 */ return 1;elseif ( place(x,y) ) /* 检查是否可放置皇后 */ {pad[x][y] = 1; /* 放置皇后 */for ( i = 0; i < MAXQUEEN; i++)for ( j = 0; j < MAXQUEEN; j++){/* 递归调用放置下一个皇后 */result += put_queen(i,j,times + 1); if ( result > 0 )break;}if ( result > 0 ) /* 找到了解 */ return 1;else{pad[x][y] = 0; /* 清除皇后 */ return 0;}}elsereturn 0;}/* ---------------------------------------- *//* 检查皇后是否有相互攻击 *//* ---------------------------------------- */(End)递归冯文科一、递归的基本概念。