递归算法
递归算法及经典例题详解
递归算法及经典例题详解
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.递归算法的应用实例4.递归算法的时间复杂度和优化方法5.总结正文:递归算法是一种自调用算法,通过将问题分解为更小的子问题来解决问题。
它在计算机科学和数学领域中广泛应用,具有可读性和实用性。
下面详细介绍递归算法的执行过程、应用实例、时间复杂度和优化方法。
一、递归算法的定义和基本原理递归算法是一种算法,它通过将问题分解为更小的子问题来解决问题。
这些子问题与原始问题具有相似的特征,从而使得算法可以通过重复调用自身来解决这些子问题。
在递归算法中,有一个基本情况(base case)和递归情况(recursive case)。
基本情况是问题规模足够小,可以直接给出答案的情况;递归情况则是将问题分解为更小的子问题,并重复调用算法本身来解决这些子问题。
二、递归算法的执行过程1.初始化:定义问题的初始条件,通常是基本情况。
2.判断基本情况:如果问题规模足够小,直接给出答案。
3.划分问题:将问题分解为更小的子问题,并确保这些子问题与原始问题具有相似的特征。
4.递归调用:将子问题传递给算法本身,重复执行步骤1-3,直到基本情况出现。
5.合并结果:将递归调用返回的结果合并,得到最终答案。
三、递归算法的应用实例1.计算阶乘:递归算法可以用于计算一个正整数的阶乘。
例如,计算5的阶乘:```def factorial(n):if n == 0:return 1else:return n * factorial(n-1)```2.计算Fibonacci 数列:递归算法可以用于计算Fibonacci 数列。
例如,计算第n个Fibonacci 数:```def fibonacci(n):if n == 0:return 0elif n == 1:return 1else:return fibonacci(n-1) + fibonacci(n-2)```四、递归算法的时间复杂度和优化方法1.时间复杂度:递归算法的时间复杂度通常为O(2^n),其中n为问题的规模。
常见递归算法
常见递归算法
常见的递归算法包括以下几种:
1. 斐波那契数列:斐波那契数列是一个经典的递归问题,每个数都是前两个数的和。
通过递归可以很容易地计算出斐波那契数列的每一项。
2. 树的遍历:在树的遍历中,递归是一种常见的实现方式。
例如,前序遍历、中序遍历和后序遍历都可以通过递归算法来实现。
3. 汉诺塔问题:汉诺塔问题是一个经典的递归问题,需要将一系列圆盘从一个柱子移动到另一个柱子,且在移动过程中不能将较大的圆盘放在较小的圆盘上面。
递归算法可以有效地解决这个问题。
4. 快速排序算法:快速排序是一种常用的排序算法,它采用了分治法的思想,通过递归将数组分成两部分并对其进行排序。
5. 归并排序算法:归并排序也是一种基于分治思想的排序算法,通过递归将数组分成子数组,然后合并它们以得到排序后的数组。
6. 二分查找算法:二分查找是一种在有序数组中查找特定元素的高效算法。
通过将数组一分为二并递归地在子数组中查找,可以快速缩小查找范围。
7. Tower of Hanoi(汉诺塔)问题:这是一个经典的数学谜题,需要将一系列圆盘从一个柱子移动到另一个柱子,遵循特定的规则。
递归算法可以用来解决这个问题。
这些只是一些常见的递归算法示例,递归在很多其他问题和算法中也有广泛应用。
递归的优点是代码简洁易懂,但需要注意防止递归深度过大导致栈溢出等问题。
在实际应用中,需要根据具体情况选择合适的算法和数据结构来解决问题。
递归算法 递推公式求解
递归算法递推公式求解递归算法是一种自我调用的算法,它通过不断将问题分解为更小的子问题来求解问题。
递归算法的核心是递推公式,也称为递归式,它描述了如何将问题分解为子问题,并如何从子问题的解中得到原问题的解。
递推公式通常具有以下形式:T(n) = aT(n/b) + f(n)其中,T(n) 表示问题规模为n 时的时间复杂度,a 表示每次递归调用的次数,b 表示每次递归调用后问题规模缩小的比例,f(n) 表示除了递归调用外的其他操作的时间复杂度。
为了求解递推公式,我们可以使用以下方法:1.迭代法:通过迭代递推公式的方式逐步计算出T(n) 的值。
这种方法比较直观,但对于较大的n 值,迭代次数可能非常多,计算量也会非常大。
2.替换法:通过猜测T(n) 的形式,并将其代入递推公式中进行验证。
如果猜测正确,则可以得到T(n) 的解。
这种方法需要对问题有一定的了解和猜测能力。
3.大师定理:大师定理是一种求解递推公式的通用方法。
它可以根据递推公式的形式,直接给出T(n) 的时间复杂度。
大师定理有多种形式,其中最常用的是以下三种:a. 如果f(n) = O(n^c),其中c < log_b(a),则T(n) = O(n^log_b(a))。
b. 如果f(n) = O(n^c),其中c = log_b(a),则T(n) = O(n^c * log_n)。
c. 如果f(n) = O(n^c),其中c > log_b(a),且对于所有足够大的n,有af(n/b) <= f(n),则T(n) = O(f(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)
应用示例:
使用排列组合递归算法可以解决很多与排列和组合相关的问题,例如:给定一个数组,求数组中所有元素的排列/组合数、给定一个集合,求集合的所有子集等。
注意事项:
在使用递归算法时需要注意避免出现无限递归的情况,需要对递归终止条件进行正确的设置。
另外,由于递归算法会涉及到大量的重复计算,因此在处理大规模数据时可能会效率较低,可以考虑使用动态规划等优化方法来提高算法的效率。
算法之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)差消化简:利用两个方程相减,将右边的项尽可能消去,以达到降阶的目的。
递归算法
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时等式 成立相似)。
递归与分治算法
递归与分治算法
递归和分治算法是计算机科学中两种常见的算法设计技术。
递归是一种直接或间接调用自身函数或者方法的算法。
在递归算法中,函数在其定义中使用了函数自身的调用。
递归算法通常用于解决需要重复执行相同任务的问题,例如遍历树结构、递归搜索等。
递归算法的优点是代码简洁、易于理解,但需要注意递归深度的限制以及可能引发栈溢出的问题。
分治算法是一种将问题分解为多个子问题,并分别解决子问题的算法。
分治算法通过将大问题分解为小问题,并将小问题的解合并成大问题的解来解决问题。
分治算法通常用于排序、查找、矩阵乘法等问题。
分治算法的优点是可以将复杂问题分解为简单问题,降低问题的复杂度,但需要注意分解的子问题必须是相互独立的。
在实际应用中,递归和分治算法通常结合使用。
例如,快速排序算法就是一种典型的分治算法,它通过选择一个基准元素,将数组分为两个子数组,并对每个子数组递归地进行排序,最终合并两个有序子数组得到排序后的数组。
总之,递归和分治算法是计算机科学中重要的算法设计技术,它们可以有效地解决许多复杂的问题。
在实际应用中,需要根据问题的特点选择合适的算法,并注意算法的时间复杂度和空间复杂度。
递归算法
前言说白了递归就象我们讲的那个故事:山上有座庙,庙里有个老和尚,老和尚在讲故事,它讲的故事是:山上有座庙,庙里有个老和尚,老和尚在讲故事,它讲的故事是:……也就是直接或间接地调用了其自身。
就象上面的故事那样,故事中包含了故事本身。
因为对自身进行调用,所以需对程序段进行包装,也就出现了函数。
函数的利用是对数学上函数定义的推广,函数的正确运用有利于简化程序,也能使某些问题得到迅速实现。
对于代码中功能性较强的、重复执行的或经常要用到的部分,将其功能加以集成,通过一个名称和相应的参数来完成,这就是函数或子程序,使用时只需对其名字进行简单调用就能来完成特定功能。
例如我们把上面的讲故事的过程包装成一个函数,就会得到:void Story(){puts("从前有座山,山里有座庙,庙里有个老和尚,老和尚在讲故事,它讲的故事是:");getchar();//按任意键听下一个故事的内容Story(); //老和尚讲的故事,实际上就是上面那个故事}函数的功能是输出这个故事的内容,等用户按任意键后,重复的输出这段内容。
我们发现由于每个故事都是相同的,所以出现导致死循环的迂回逻辑,故事将不停的讲下去。
出现死循环的程序是一个不健全的程序,我们希望程序在满足某种条件以后能够停下来,正如我们听了几遍相同的故事后会大叫:“够了!”。
于是我们可以得到下面的程序:#include<stdio.h>const int MAX = 3;void Story(int n);//讲故事int main(void){Story(0);getchar();return 0;}void Story(int n){if (n < MAX){puts("从前有座山,山里有座庙,庙里有个老和尚,老和尚对小和尚说了一个故事:");getchar();Story(n+1);}else{printf("都讲%d遍了!你烦不烦哪?\n", n);return ;}}上面的Story函数设计了一个参数n,用来表示函数被重复的次数,当重复次数达到人们忍受的极限(MAX次)时,便停下来。
递归算法
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; //返回值放在全局变量里 }
算法设计与分析-递归法
b)
cn k
n1 n1
O (nlog b a ) T (n) O(nk log b n) O(nk )
a bk a bk a bk
但是并非所有的递推式都可以用公式法求解。 例T(n)=2T(n/2)+nlogn 由于a=2, b=2, f(n)=nlogn和nlogba=n。看起来似乎属于 主定理情况(3),但事实上f(n)只是渐近大于n,但并不 是多项式大于n。f(n)与的nlogba比值是log n,对于任何 正数,log n渐近小于n,所以,此例不能运用定理。
1 替换方法
替换方法要求首先猜测递推式的解,然后用归纳法证明。
例2.2 T(n) 2T(n / 2) n
需要注意在上述证明过程中,没有考虑初始条件,而初始条 件是归纳法成立的基础。上例归纳证明的初始条件是是 T(1)≤c,只要选择足够大的c≥1即成立。
2.迭代方法
迭代方法的思想是扩展递推式,将递推式先转换成 一个和式,然后计算该和式,得到渐近复杂度。 例2.4 使用迭代方法分析 T (n) 2T (n / 2) n2
本章要点
• 递归算法特性
• 递推关系 • 递归算法的应用
章节内容
2.1 递归算法 2.1.1 递归算法特性 2.1.2 递归算法的执行过程 2.1.3 递推关系
2.2 递归法应用举例
2.3 典型问题的C++程序
2.4 小结
2.1 递归法
2.1.1 递归算法的特性
若一个算法直接的或间接的调用自己本身,则称这个算 法是递归算法。递归本质上也是一种循环的算法结构,它把较 复杂的计算逐次归结为较简单的情形的计算,直到归结到最简 单情形的计算,并最终得到计算结果为止。
递归算法
递归算法是把问题转化为规模缩小了的同类问题的子问题。
然后递归调用函数(或过程)来表示问题的解。
一个过程(或函数)直接或间接调用自己本身,这种过程(或函数)叫递归过程(或函数)。
算法特点:
递归算法是直接或间接调用自身的算法。
在计算机程序设计中,递归算法对于解决一大类问题非常有效,它经常使算法的描述简洁明了且易于理解。
解决问题的递归算法特点:
(1)递归是在过程或函数中调用自身。
(2)使用递归策略时,必须有明确的递归结束条件,称为递归退出。
(3)递归算法求解问题通常看起来很简洁,但是递归算法求解问题的效率很低。
因此,通常不建议使用递归算法来设计程序。
(4)在递归调用过程中,系统打开了一个堆栈来存储每个层的返回点和局部变量。
太多的递归很容易导致堆栈溢出。
因此,通常不建议使用递归算法来设计程序。
折叠递归算法要求
递归算法所体现的“重复”一般有三个要求:
一是每次调用在规模上都有所缩小(通常是减半);
二是相邻两次重复之间有紧密的联系,前一次要为后一次做准备(通常前一次的输出就作为后一次的输入);
三是在问题的规模极小时必须用直接给出解答而不再进行递归调用,因而每次递归调用都是有条件的(以规模未达到直接解答的大小为条件),无条件递归调用将会成为死循环而不能正常结束。
递归及递归算法图解
递归问题的提出
第一步:将问题简化。 – 假设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)两种不同的递归函数?
递归和递推:比较下面两个示例
递归算法
问题分析:我们根据给出的样例可知:每次输出的 结果都是由前一次的结果变化而来的,也就是问题 每推进一步其结果仍维持与原问题的关系,可见采 用递归算法比较合适。其算法的大致过程如下: 1、利用循环语句实现对前一次的输出结果从后向 前找一个a[i],使得a[i]到a[w]的字符都在s、t规定的 字母范围内,以确定本次的输出结果。 2、当输出结果达到5个,结束递归;如果没那么多 Jam数字,当第一次被入栈的循环结束时,递归结 束。
上楼梯问题
递归关系: f(1)=1; f(2)=2; f(n)=f(n-1)+f(n-2); (n≥3)
已知:ack(m,n)函数的计算公式如下:
请计算ack(m,n)的值。(m,n<=5)
用递归算法求解两个整数的最大公约数
分析:辗转相除法 。即:两个整数相除,看 其余数是否为0。若余数为0,则除数即为所 求最大公约数;若余数不为0,就将除数作为 被除数,余数作为除数,继续相除,循环往 复,直到余数为0。
数的计算
问题描述 我们要求找出具有下列性质数的个数(包含输入的自然数n): 先输入一个自然数n(n<=1000),然后对此自然数按照如下方法进行 处理: 1. 不作任何处理; 2. 在它的左边加上一个自然数,但该自然数不能超过原数的一半; 3. 加上数后,继续按此规则进行处理,直到不能再加自然数为止. 样例: 输入: 6 满足条件的数为 6 (此部分不必输出) 16 26 126 36 136 输出: 6
问题分析:对于这个问题,首先,我们得具备对一 颗二叉树能熟练并且正确写出它的前序、中序、后 序序列的能力,才能编写程序解决问题。 我们根据题中给出的中序及后序序列,可以找出该 树根结点及左右子树。同样对于左右子树,再根据 它们各自的中序及后序序列,又能找出它们的根结 点及它们的左右子树。由此可见,该问题能够被递 归描述。当最后的序列为空时,递归无法再进行下 去,就是递归结束的边界条件。
递归算法条件
递归算法条件
递归算法是一种以函数自身调用为基础的算法。
在递归算法中,函数
通过调用自身来解决问题。
但是,递归算法是有条件的,必须满足以
下条件。
1. 基准情况
递归算法是通过将问题划分为更小的子问题来解决的。
每一个子问题
都是规模更小的原问题的一个副本。
递归算法必须有一个基准情况,
即问题越来越小,最终达到一个可以直接解决的规模。
在递归过程中,当问题达到基准情况时,递归终止,算法也就结束了。
2. 自相似性
递归算法中,每个子问题与原问题的形式相同,只是规模更小。
这种
相似性使得算法能够将问题划分为一系列更小的问题,从而解决原问题。
3. 每次递归必须接近基准情况
在递归算法中,必须保证每一次递归都接近基准情况。
如果每一次递
归离基准情况越来越远,算法就会陷入无限递归的过程中,最终导致栈溢出等错误。
4. 循环调用自身
递归算法的核心是函数自身调用。
每次递归都是函数自己再次调用自身。
这种调用方式使得递归算法可以自动地将问题转化为更小的子问题,从而解决原问题。
总之,递归算法是一种非常强大的算法,但它也受到条件的限制。
只有当条件满足时,递归算法才能够正确地解决问题,并达到预期的效果。
因此,在编写递归算法时,必须仔细地考虑问题的基准情况和如何递归调用自身,以确保算法的正确性和效率。
递归算法和递推算法的原理
递归算法和递推算法的原理-概述说明以及解释1.引言1.1 概述递归算法和递推算法是编程中常用的两种算法思想,它们都是一种问题解决的方法论。
递归算法通过将一个大问题分解为一个或多个相同的小问题来解决,而递推算法则是通过给定初始条件,通过逐步推导出后续结果来解决问题。
递归算法是一种自调用的算法,它将一个问题划分为更小规模的相同子问题,并通过调用自身来解决这些子问题。
每个子问题的解决方案被合并以形成原始问题的解决方案。
递归算法具有简洁的代码结构和易于理解的逻辑。
它在一些问题上能够提供高效的解决方案,例如树的遍历、图的搜索等。
递推算法则是从已知的初始条件开始,通过根据给定的递推公式或规则,逐步计算出后续的结果。
递推算法是一种迭代的过程,每一次迭代都会根据已知条件计算得出下一个结果。
递推算法常应用于数学问题,求解斐波那契数列、阶乘等等。
递归算法和递推算法在解决问题时的思路不同,但也存在一些相似之处。
它们都能够将大问题分解成小问题,并通过解决这些子问题来获得问题的解决方案。
而且递归算法和递推算法都有各自适用的场景和优缺点。
本文将详细介绍递归算法和递推算法的原理、应用场景以及它们的优缺点。
通过比较和分析两者的差异,帮助读者理解和选择合适的算法思想来解决问题。
1.2文章结构文章结构部分的内容可以描述文章的整体框架和各个章节的内容概要。
根据给出的目录,可以编写如下内容:文章结构:本文主要探讨递归算法和递推算法的原理及其应用场景,并对两者进行比较和结论。
文章分为四个部分,下面将对各章节的内容进行概要介绍。
第一部分:引言在引言部分,我们将对递归算法和递推算法进行简要概述,并介绍本文的结构和目的。
进一步,我们将总结递归算法和递推算法在实际问题中的应用和重要性。
第二部分:递归算法的原理在第二部分,我们将详细讨论递归算法的原理。
首先,我们会给出递归的定义和特点,探索递归的本质以及递归算法的基本原理。
其次,我们将展示递归算法在不同的应用场景中的灵活性和效果。
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`为查找范围的起始和结束位置。
常用特殊算法
6.2.1 递推算法的适用性
但并不是所有的递归算法都适合改写成递推算 法, 最起码的条件是求解过程允许从有明确结果的低 阶问题开始。阶乘问题就允许从 1!开始,推算到我 们希望的某一阶为止,因此,采用递推算法来求解阶 乘问题就比递归算法好得多。 但有很多递归算法的求 解起点是有限制的,不允许从低阶问题开始求解,也 就不能改写成递推算法。例如有名的“梵塔问题”就 是这样, 一阶梵塔的解法是明确的, 如果 N 阶梵塔的 解法已知,就可以推出 N+1 阶梵塔的解法,看起来 很适合采用递推算法, 但该问题就是不允许从一阶梵 塔开始,必须从 N 阶梵塔开始。 “梵塔问题”已经成 为递归算法的经典实例, 没有其它算法比用递归算法 更直观有效。
6.3.1 回溯算法的特点
回溯算法有以下基本特点: 问题的求解必须是由有限的若干部分组成的,例如一条从迷宫入口到迷宫出口的路 径是由若干(中间没有分支的) “路段”组成的;一种服装的裁剪下料方案是由各 个衣片的摆放位置组成的; 一种配方是由各种原料的取舍用量组成的; 一局棋局是 由开局、中盘、残局、结局各阶段的下法组成的。如果我们把问题解的所有可能的 组成部分称为“元素”的话,那么元素的范围必须是有限的,例如配方问题中原料 的种类和用量是有一定范围的。 一个问题如果有多个解的话, 各个解的区别在于它 们的组成元素的取舍不同。问题的一个解的部分元素构成“部分解” ,不同解之间 可以有相同的“部分解” ,例如配方 A 包含有 6 种原料,配方 B 包含有 7 种原料, 两种配方中有 4 种原料是相同的,它们都可以是符合要求的配方。 回溯算法求解问题的过程是由“部分解”向“完整解”推进的过程(开始时部分解 是空的,一个元素也没有) 。推进的方法是在“部分解”的基础上增加一个新元素, 如果新增加这个元素之后仍然满足问题的规定条件(约束条件) ,我们就得到一个 新的“部分解” ,然后再试着增加一个新的元素。如果新增加这个元素之后破坏了 问题的规定条件,我们就将这个新元素取出来, “回溯”到没有增加这个新元素时 的状态,另外选取别的元素再试。将这种试探一直进行下去,当“部分解”完全满 足问题的条件时,这时的“部分解”就称为“完整解” ,可以将其输出。当搜索完 全部可能组合之后仍然没有得到“完整解” ,就证明该问题无解。 在回溯算法进行的过程中,各步的处理方法都是相同的,符合递归算法的特点,因 此,回溯算法中一般都配合递归算法来进行。在递归的过程中,可供选择的元素范 围越来越小, 约束条件也越来越苛刻, 从而保证递归过程可以在有限的时间之内结 束。在递归过程中,问题的“部分解”是作为全局数据处理,而当前可供选择的元 素范围和当前约束条件的动态值是作为局部数据处理(需要用户堆栈保护) 。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
一.递归算法概述
程序调用自身的编程技巧称为递归( recursion)。
一个过程或函数在其定义或说明中又直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的能力在于用有限的语句来定义对象的无限集合。
用递归思想写出的程序往往十分简洁易懂。
二.递归算法的特点
递归算法是一种直接或者间接地调用自身算法的过程。
在计算机编写程序中,递归算法对解决一大类问题是十分有效的,它往往使算法的描述简洁而且易于理解。
递归算法解决问题的特点:
(1) 递归就是在过程或函数里调用自身。
(2) 在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。
(3) 递归算法解题通常显得很简洁,但递归算法解题的运行效率较低。
所以一般不提倡用递归算法设计程序。
(4) 在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。
递归次数过多容易造成栈溢出等。
所以一般不提倡用递归算法设计程序。
三.递归算法要求
递归算法所体现的“重复”一般有三个要求:
一是每次调用在规模上都有所缩小(通常是减半);
二是相邻两次重复之间有紧密的联系,前一次要为后一次做准备(通常前一次的输出就作为后一次的输入);
三是在问题的规模极小时必须用直接给出解答而不再进行递归调用,因而每次递归调用都是有条件的(以规模未达到直接解答的大小为条件),无条件递归调用将会成为死循环而不能正常结束。
四.例子(用从C++描述):
行数程序
#include <iostream>
using namespace std;
0 void p (int w)
{
1 if(w>0)
{
2 cout<<w<<" ";
3 p(w-1);
4 p(w-1);
5 }
6 }
void main()
{
int a;
cin>>a;
p(a);
}
当输入a=4后的打印结果:
当p(0)执行完了,就会执行p(1)中的语句5(所以在方格a中,填“5”)。
因为w=0不满足语句1,所以直接跳到语句5、6,从而p(0)执行完毕,p(0)要进行“出栈”操作。
8
由于p(0)执行完成,且p(0)的方格a中为5,因此继续执行p(1)的语句5 (最后一句),所以p(1)执行完毕,p(1)要进行“出栈”操作。
9.
由于p(1)执行完成,且p(1)的方格a中为4,因此继续执行p(2)的语句4 :p(w-1);又由于p(2)方格c中w值为2,所以调用p(1)。
当p(1)执行完了,就会执行p(2)中的语句5(所以在方格a中,填“5”)。
执行p(1)的语句2:cout<<w; 打印1(这是第五个结果)当执行到语句3,还要调用p(0),只有p(0)执行完了,才能继续执行p(1)。
因为w=0不满足语句1,所以直接跳到语句5、6,从而p(0)执行完毕,p(0)要进行“出栈”操作。
由于p(0)执行完成,且p(0)的方格a中为4,因此继续执行p(1)的语句4 :
p(w-1);又由于p(1)方格c中w值为1,所以调用p(0)。
13.开始调用p(0)
当p(0)执行完了,就会执行p(1)中的语句5(所以在方格a中,填“5”)。
因为w=0不满足语句1,所以直接跳到语句5、6,从而p(0)执行完毕,p(0)要进行“出栈”操作。
由于p(0)执行完成,且p(0)的方格a中为5,因此继续执行p(1)的语句
5 (最后一句),所以p(1)执行完毕,p(1)要进行“出栈”操作。
注意:其实步骤10~15重复了步骤4~9,因为它们都调用的P(1)
16.
由于p(2)执行完成,且p(2)的方格a中为4,因此继续执行p(3)的语句
4 :p(w-1);又由于p(3)方格c中w值为3,所以调用p(2)。
当p(2)执行完了,就会执行p(3)中的语句5(所以在方格a中,填“5”)。
执行p(2)的语句2:cout<<w; 打印2(这是第六个结果)同上面的情况相同,当执行到语句3,还要调用p(1),只有p(1)执行完了,才能继续执行p(2)。
18.开始调用p(1)
省略……
注意:其实步骤17~29重复了3~15,因为它们都调用的P(2) 在这步骤中,又打印了2 1 1(见步骤3、4、10)
C/C++的编译器对调用函数和被调用函数之间的链接和信息交换是通过栈来完成的。
通常,当在一个函数的运行期间调用另外一个函数时,在运行被调用
函数之前,系统需要完成三件事:(1)将所有的实在参数、返回地址等信息传递给被调用函数保存;(2)为被调用函数的局部变量分配存储区;(3)将控制转移到被调用函数的入口。
而从被调用函数返回调用函数之前,系统也应完成三件工作:(1)保存被调用函数的计算结果;(2)释放被调用函数的数据区;(3)依照被调用函数保存的返回地址将控制转移到被调用函数。
当有多个函数构成嵌套调用时,按照“后调用先返回”的原则,上述函数之间的信息传递和控制信转移必须通过“栈”空间来完成,即系统将整个程序运行时所需的数据空间安排在一个栈中,每当调用一个函数时,就为它在栈顶分配一个存储区;每当从一个函数退出时,就释放它的存储区,则当前正运行的函数的数据区必在栈顶,此可以参阅函数调用存储分配问题等相关资料。
一个递归函数的运行过程类似于多个函数的嵌套调用,只是调用函数和被调用函数是自身而已。
因此,和每次调用相关的一个重要概念是递归函数运行的“层次”。
假设调用该递归函数的主函数为第0层,则从主函数调用递归函数为进入第1层;从第i层递归调用不本函数为进入“下一层”,即第i+1层。
反之,退出第i层递归应返回至“上一层”,即第i-1层。
为了保证递归函数正确执行;系统需设立一个“递归工作栈”作为整个递归函数运行期间使用的数据存储区。
每一层递归所需要的信息构成一个“工作记录”,其中包含了所有的实在参数、所有的局部变量以及上一层的返回地址。
每进入一层递归,就产生一个新的工作记录压入栈顶;每退出一层递归,就从栈顶弹出一个工作记录,则当前执行层的工作记录必是递归工作栈栈顶的工作记录,即“活动记录”,并称指示活动记录的栈顶指针为“当前环境指针”。
实际上,在调用函数和被调用函数之间不一定传递参数的值,也可以传递参数的地址等。
通常,每个程序设计语言都有它自己约定的传递方法(包括被调用函数的执行结果如何返回调用函数等问题)。
在C/C++下允许使用递归,由于递归函数反复调用自身,导致系统创建了一个庞大的栈内存空间。
递归的目的是简化程序设计,使程序易读。
但递归增加了系统开销:时间上,执行调用于返回的额外工作要占用CPU时间;空间上,因为调用函数和被调用函数之间的链接和信息交换需要通过栈来进行,随着每次递归一次,栈内存就多用一截。
所以,一个简单的递归函数也可能严重损害运行性能,更严重的是,一次递归调用可能产生一层接一层的递归调用,这会超出程序员的控制,并且对堆栈的需求也会超出可用栈空间范围。
六.结束语
通常,在时间和空间上递归开销较大,它并非是解决问题的高效方法,但递归仍然是一个重要的设计和编程工具。
许多算法用递归更便于叙述和解决,它们很自然地就可以用终止条件和递归步骤实现递归。
虽然递归不是面向对象
的概念,它却具有面向对象程序设计所具有的好处。
它允许程序员管理算法中的一些关键逻辑部件而隐藏其复杂的实现细节,使得代码结构清晰,程序易懂,而且正确性容易得到证明。
关于何时使用递归没有硬性规定,随着计算机硬件性能的不断提升,程序在更多的场合应优先考虑可读性而非高效。
所以,鼓励用递归函数实现程序思想。