递归调用详解,分析递归调用的详细过程

合集下载

递归算法及经典例题详解

递归算法及经典例题详解

递归算法及经典例题详解
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的值,让递归进入下一层。

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

递归函数的执行过程java

递归函数的执行过程java

递归函数的执行过程java
递归函数是指在函数中调用自身的函数。

它在一定程度上可以简化代码和提高程序运行效率,但也容易导致栈溢出等问题。

在Java 语言中,递归函数的执行过程可以概括为以下几个步骤:
1. 确定递归终止条件:递归函数必须有一个终止条件,否则就会无限递归下去,导致栈溢出。

通常情况下,终止条件是一个简单的判断语句,例如if语句或者switch语句。

2. 调用自身:如果递归终止条件未满足,那么就要调用自身函数,直到满足终止条件为止。

在调用过程中,每一次函数调用都会将函数的参数和返回地址压入栈中,等待函数执行完毕后再弹出,以便继续执行上一级函数。

3. 传递参数:每次递归调用都会传递不同的参数值,这些参数值决定了递归函数的执行过程和结果。

在Java中,递归函数的参数传递方式可以是按值传递、按引用传递或者混合传递。

4. 计算返回值:当递归终止条件满足时,递归函数就会开始计算返回值,并将返回值传回上一级函数。

在Java中,递归函数的返回值可以是任意类型的数据,包括基本类型、对象、数组等。

5. 弹出栈帧:当递归函数执行完毕并返回结果后,就会弹出当前函数的栈帧,将控制权交给上一级函数。

这时,上一级函数就可以利用返回值继续执行自己的逻辑。

如果递归函数的调用层数过多,就可能导致栈溢出等问题。

总之,递归函数的执行过程十分复杂,需要谨慎使用。

在编写递
归函数时,应该尽量避免无限递归和栈溢出等问题,同时也要注意递归终止条件的设置和参数传递方式的选择。

数据结构之递归

数据结构之递归

数据结构之递归Ⅲ递归的三⼤要素// 算 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 项的值是多少。

函数的递归调用

函数的递归调用

函数的递归调用递归调用是指一个函数把自己调用自身的方法。

它包括一个终止条件和一个调用自身的指令,由它构成的一种编程技巧。

递归调用有助于我们更有效地解决计算机问题,特别是当这些问题可以递归处理时,它们可以节省空间和时间。

1. 什么是递归调用递归调用是一种编程技巧,它涉及到函数自身调用自身,而且必须包括一个终止条件,即程序能知道自己停止调用自身的条件。

它可以更高效地解决计算机问题,是一种编程实用技巧。

2. 递归调用优势(1)递归调用能够比其它的计算机程序算法更高效地解决问题;(2)它可以保护代码的简洁,从而使其更容易理解和维护;(3)它可以节省空间和时间;(4)它可以实现过滤和模糊匹配。

3. 递归调用的编写递归调用包括一个终止条件和一个调用自身的指令。

编写递归程序有以下三个要点:(1)找到问题的终止条件:首先要找到能够停止调用自身的条件,这个条件被称为终止条件,也称为基层条件;(2)带有变量的编写:递归是将大问题拆解成小问题来求解,所以为了能够拆解出更小的问题,我们必须在编写的时候加上一些变量;(3)调用自身:递归对问题的解法十分重要,即调用函数自身。

当函数取得了问题的更小的部分答案之后,调用自身函数,就可以获得完整的答案。

4. 递归调用的应用(1)实现排序算法:递归调用可以实现许多常见的排序算法,比如快速排序、归并排序等;(2)处理树形结构:递归调用可以非常有效地处理树形结构的数据,例如,深度优先搜索和广度优先搜索;(3)处理数学表达式:可以用递归调用解析并处理复杂的数学表达式,例如,解析逻辑表达式;(4)处理字符串和文本:可以用递归调用处理字符串和文本,例如,过滤HTML标签。

递归调用算法

递归调用算法

递归调用算法递归调用算法是一种重要的算法思想,它可以用较简单的代码来解决复杂问题。

在计算机科学中,递归(recursion)是一种常见的技术,指的是一个函数在调用自身的过程。

递归算法通常包括两个部分:基本情况(base case)和递推情况(recursive case)。

基本情况通常是指递归需要结束的条件,不再调用自身,而递推情况则是指递归调用自身的代码。

递归算法有许多重要应用,例如计算斐波那契数列、二叉树的遍历、字符串匹配等。

这些应用都可以用递归算法来实现,使得代码更加简单易懂。

在本文中,我将讨论递归调用算法的基本原理及其在实践中的应用。

递归算法的基本原理递归算法的基本原理是函数调用自身。

在调用函数时,如果函数需要调用自己,那么这个过程就称为递归调用。

在递归调用过程中,每一次函数调用都会使用独立的内存空间来存储相关的变量值,直到函数执行结束并返回结果。

递归算法通常涉及递归函数的参数和返回值。

递归函数的参数在每次调用时会改变,而返回值则是递归结束时的最终结果。

1. 定义递归函数(包括函数名、参数列表、返回值等)。

2. 判断递归结束条件(即基本情况)。

3. 在递归情况下,调用自身,并传递相应的参数。

4. 处理递归返回值,返回最终结果。

递归算法的典型应用之一是计算斐波那契数列。

在斐波那契数列中,每个数都是前两个数相加得到的。

递归算法可以将斐波那契数列的计算过程转化为一个递归调用的过程。

函数调用自身来计算前面两个数的和,然后返回结果。

```int fibonacci(int n){if (n == 0)return 0;else if (n == 1)return 1;elsereturn fibonacci(n-1) + fibonacci(n-2);}```在上面的代码中,递归函数 `fibonacci` 接受一个参数 `n`,它代表要计算斐波那契数列的第几个数。

当 `n` 等于 0 或 1 时,递归结束,这是基本情况。

编程语言中的递归方法解析

编程语言中的递归方法解析

编程语言中的递归方法解析在计算机编程中,递归是一种重要的方法,它允许程序通过调用自身来解决问题。

递归在算法设计和实现中起着重要的作用,它能够简化问题的解决过程,提高代码的可读性和可维护性。

本文将深入探讨编程语言中的递归方法,探索其原理、应用和一些常见的注意事项。

一、递归的原理递归是一种自我调用的方法,它将问题划分为更小的子问题,并通过解决子问题来解决原始问题。

递归方法通常包含两个部分:基本情况和递归情况。

基本情况是指问题的最小规模,它通常是递归方法的终止条件。

递归情况是指将问题分解为更小规模的子问题,并通过递归调用解决这些子问题。

递归方法的实现通常使用函数或过程的形式。

在函数式编程语言中,递归方法是一种常见的编程范式。

例如,在Lisp和Scheme等语言中,递归是一种基本的控制结构,用于解决各种问题。

二、递归方法的应用递归方法在许多领域中都有广泛的应用。

其中一个常见的应用是计算数列中的斐波那契数列。

斐波那契数列是一个无限序列,其中每个数字都是前两个数字的和。

通过递归方法可以轻松地计算斐波那契数列的第n个数字。

另一个常见的应用是树的遍历。

树是一种常见的数据结构,它由节点和边组成。

通过递归方法可以遍历树的所有节点,从而实现对树的操作和分析。

递归方法还可以用于解决复杂的数学问题,如计算阶乘、排列组合等。

通过递归方法,可以将这些问题简化为更小规模的子问题,从而提高解决问题的效率。

三、递归方法的注意事项尽管递归方法具有许多优点,但在使用时也需要注意一些问题。

首先,递归方法可能会导致堆栈溢出。

每次递归调用都会将一些信息存储在堆栈中,如果递归调用的层数过多,堆栈可能会超出其容量,导致程序崩溃。

因此,在使用递归方法时,需要确保递归的深度不会过大,或者使用尾递归等优化方法来减少堆栈的使用。

另一个需要注意的问题是递归方法的性能。

由于递归方法涉及多次函数调用和堆栈操作,它可能比迭代方法更慢。

在某些情况下,可以使用迭代方法或其他更高效的算法来替代递归方法。

递归调用详解分析递归调用的详细过程

递归调用详解分析递归调用的详细过程

递归调用详解分析递归调用的详细过程递归调用是一种在函数内部调用自身的方法。

当一个函数被调用时,它会在内存中分配栈帧来存储函数的局部变量、参数和返回地址。

在递归调用中,每次调用函数时都会分配一个新的栈帧,这使得函数能够多次重复执行相同的操作。

为了更好地理解递归调用的详细过程,我们可以通过一个经典的例子来进行分析,计算阶乘。

阶乘可以用递归的方式来计算,即n!=n*(n-1)!假设我们调用一个名为 factorial 的函数来计算阶乘,其代码如下:```int factorial(int n)if (n == 0)return 1;} elsereturn n * factorial(n - 1);}```现在我们来详细分析一下调用 factorial(4) 的过程:1. 首先,我们调用 factorial(4)。

由于 n 不等于 0,执行 else分支的代码。

2. 在执行 return 语句之前,需要先计算 factorial(n - 1) 的值。

这时我们需要调用 factorial(3)。

3. 继续重复步骤 2,我们需要调用 factorial(2)。

4. 再次重复步骤 2,我们需要调用 factorial(1)。

5. 重复步骤 2,我们需要调用 factorial(0)。

6. 当 n等于 0 时,执行 if 分支的代码,直接返回 17. 现在我们可以回到步骤 2,计算 factorial(1) * 1 的值,即 1* 1 = 18. 继续回到步骤 3,计算 factorial(2) * 1 的值,即 2 * 1 = 29. 再次回到步骤 4,计算 factorial(3) * 2 的值,即 3 * 2 = 610. 最后回到步骤 1,计算 factorial(4) * 6 的值,即 4 * 6 =24通过以上步骤,我们可以看到递归调用的详细过程。

每次递归调用时,会将当前的参数值n减1,并将下一次递归调用的结果与当前的n相乘,最后返回相乘的结果。

递归算法及经典例题详解

递归算法及经典例题详解

递归算法及经典例题详解大部分人在学习编程时接触的第一个算法应该就是递归了,递归的思想其实很好理解,就是将一个问题拆分为若干个与本身相似的子问题,通过不断调用自身来求解。

但很多新手在实际操作中却很难正确使用到递归,有时面对问题还会有种无从下手的感觉,在此,我总结了一些解决递归问题的方法和思路,希望对你能有所帮助。

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

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

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

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

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

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

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

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

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

递归调用详解,分析递归调用的详细过程
2009年05月23日星期六 22:52
一、栈
在说函数递归的时候,顺便说一下栈的概念。

栈是一个后进先出的压入(push)和弹出(pop)式数据结构。

在程序运行时,系统每次向栈中压入一个对象,然后栈指针向下移动一个位置。

当系统从栈中弹出一个对象时,最近进栈的对象将被弹出。

然后栈指针向上移动一个位置。

程序员经常利用栈这种数据结构来处理那些最适合用后进先出逻辑来描述的编程问题。

这里讨论的程序中的栈在每个程序中都是存在的,它不需要程序员编写代码去维护,而是由运行是系统自动处理。

所谓的系统自动维护,实际上就是编译器所产生的程序代码。

尽管在源代码中看不到它们,但程序员应该对此有所了解。

再来看看程序中的栈是如何工作的。

当一个函数(调用者)调用另一个函数(被调用者)时,运行时系统将把调用者的所有实参和返回地址压入到栈中,栈指针将移到合适的位置来容纳这些数据。

最后进栈的是调用者的返回地址。

当被调用者开始执行时,系统把被调用者的自变量压入到栈中,并把栈指针再向下移,以保证有足够的空间存储被调用者声明的所有自变量。

当调用者把实参压入栈后,被调用者就在栈中以自变量的形式建立了形参。

被调用者内部的其他自变量也是存放在栈中的。

由于这些进栈操作,栈指针已经移动所有这些局部变量之下。

但是被调用者记录了它刚开始执行时的初始栈指针,以他为参考,用正或负的偏移值来访问栈中的变量。

当被调用者准备返回时,系统弹出栈中所有的自变量,这时栈指针移动了被调用者刚开始执行时的位置。

接着被调用者返回,系统从栈中弹出返回地址,调用者就可以继续执行了。

当调用者继续执行时,系统还将从栈中弹出调用者的实参,于是栈指针回到了调用发生前的位置。

可能刚开始学的人看不太懂上面的讲解,栈涉及到指针问题,具体可以看看一些数据结构的书。

要想学好编程语言,数据结构是一定要学的。

二、递归
递归,是函数实现的一个很重要的环节,很多程序中都或多或少的使用了递归函数。

递归的意思就是函数自己调用自己本身,或者在自己函数调用的下级
函数中调用自己。

递归之所以能实现,是因为函数的每个执行过程都在栈中有自己的形参和局部变量的拷贝,这些拷贝和函数的其他执行过程毫不相干。

这种机制是当代大多数程序设计语言实现子程序结构的基础,是使得递归成为可能。

假定某个调用函数调用了一个被调用函数,再假定被调用函数又反过来调用了调用函数。

这第二个调用就被称为调用函数的递归,因为它发生在调用函数的当前执行过程运行完毕之前。

而且,因为这个原先的调用函数、现在的被调用函数在栈中较低的位置有它独立的一组参数和自变量,原先的参数和变量将不受影响,所以递归能正常工作。

程序遍历执行这些函数的过程就被称为递归下降。

程序员需保证递归函数不会随意改变静态变量和全局变量的值,以避免在递归下降过程中的上层函数出错。

程序员还必须确保有一个终止条件来结束递归下降过程,并且返回到顶层。

例如这样的程序就是递归:
void a(int);
main()
{
int num=5;
a(num);
}
void a(int num)
{
if(num==0) return;
printf("%d",num);
a(--num);
}
在函数a()里面又调用了自己,也就是自己调用本身,这样就是递归。

那么有些人可能要想,这不是死循环吗?所以在递归函数中,一定要有return语句,没有return语句的递归函数是死循环。

我们分析上面的例子,先调用a(5),然后输出5,再在函数中调用本身a(4),接着回到函数起点,输出4,……,一直到调用a(0),这时发现已经满足if条件,不在调用而是返回了,所以这个递归一共进行了5次。

如果没有这个return,肯定是死循环的。

虽然递归不难理解,但是很多在在使用递归函数的时候,问题多多。

这里面一般有两个原因:一是如何往下递归,也就是不知道怎么取一个变量递归下去;二是不知道怎么终止递归,经常弄个死循环出来。

下面看几个例子:
1.求1+2+……+100的和
先分析一下。

第一递归变量的问题,从题目上看应该取1,2,……,100这些变量的值作为递归的条件;第二就是如何终止的问题,从题目上看应该是当数为100的时候就不能往下加了。

那么我们试着写一下程序。

int add(int);
main()
{
int num=1,sn;
sn=add(num);
printf("%d\n",sn);
getch();
}
int add(int num)
{
static int sn;
sn+=num;
if(num==100) return sn;
add(++num);
}
分析一下程序:前调用add(1),然后在子函数中把这个1加到sn上面。

接着调用add(2),再把sn加2上来。

这样一直到100,到了100的时候,先加上来,然后发现满足了if条件,这时返回sn的值,也就是1+2+……+100的值了。

这里有一个问题一定要注意,就是static int sn;
有些人就不明白,为什么要使用static类型修饰符,为什么不使用int sn=0;?如果使用int sn=0;这样的语句,在每次调用函数add()的时候,sn的值都是赋值为0,也就是第一步虽然加了1上来,可是第二次调用的时候,sn又回到了0。

我们前面说了,static能保证本次初始化的值是上次执行后的值,这样也就保证了前面想加的结果不会丢失。

如果你修改为int sn=0,最后结果一定是最后的100这个值而不是5050。

2.求数列s(n)=s(n-1)+s(n-2)的第n项。

其中s(1)=s(2)=1。

可以看出,终止条件一定是s(1)=s(2)=1。

递归下降的参数一定是n。

int a(int);
main()
{
int n,s;
scanf("%d",&n);
s=a(n);
printf("%d\n",s);
getch();
}
int a(int n)
{
if(n<3) return 1;
return a(n-1)+a(n-2);
}
这个题目主要说明的是,在函数中,不一定只有一个return语句,可以有很多,但是每次对归的时候只有一个起作用。

题目不难理解,这儿不分析了。

说了这些递归,其实它和函数的调用没有大的区别,主要就是一个终止条件要选好。

递归函数很多时候都能用循环来处理。

main()
{
int n=20,array[20];
int i;
for(i=0;i<n;i++)
{
if(i<2) array[i]=1;
else array[i]=array[i-1]+array[i-2];
}
printf("%d\n",array[19]);
getch();
}
上面的程序就是实现一模一样的功能的。

但是它有一个缺陷,就是n的值不是通过键盘输入来得到。

如果想通过键盘来得到n,可以这样:
main()
{
int n,i;
int s1=1,s2=1,temp
scanf("%d",&n);
for(i=3;i<=n;i++)
{
temp=s2;
s2+=s1;
s1=temp;
}
printf("%d\n",s2);
getch();
}
但是在某些场合,使用递归比使用循环要简单的多。

而且有些题目,一看就知道应该使用递归而不是循环来处理。

相关文档
最新文档