递归编程实验

一、实验目的和要求:(本次实验所涉及并要求掌握的知识点)

1.体会递归问题的两个基本条件。

2.比较递归与显式用栈实现的方法。

3.思考递归编程存在的局限性及解决方法。

4.完成实验报告。

二、实验环境:(本次实验所需要的平台和相关软件)

Windows 11

Visual Studio 2022

Dev-C++5.10

三、实验内容及步骤:(本次实验计划安排的实验内容和具体实现步骤)

1.编写把十进制正整数转换为S进制(S=2,8,16)数输出的递归算法。

2.编写出计算Fib(n)的递归算法。并分析递归算法和非递归算法的时间复杂度和空间复杂度。

3.杨辉三角形的递归实现。

4.一个射击运动员打靶,靶一共有10环,连开10枪打中90环的可能性有多少种?试用递归算法实现。 程序结果一共有92 378种可能。

5.编写一个递归算法,输出自然数1~n这n个数的全排列

6.编写GRAY码的递归算法。

四、实验过程和结果:(记录实验过程和结果、以及所出现的问题和解决方法)

1.进制转换:

(1)进制转换递归实现:

每一次取余的操作都是一致的。且终止条件存在,为n==0。

正常输入输出:                   健壮性:

(2)进制转换链栈实现:

将余数转换成字符压入栈内,随后让所有元素依序出栈,改变余数字符的存储顺序,存入字符数组内并输出。

正常输入输出:                   健壮性:

2.斐波那契数列:

(1)递归实现:第一和第二个元素为1,后续元素等于前两个元素之和。

<1>计算时间复杂度:

调用总次数为2^(N-1)-1,则时间复杂度为O(2^n)

<2>计算空间复杂度:

递归的斐波那契数列的空间复杂度是O(n)。Fib(N-1)一路沿Fib(2)返回后消毁栈帧,调用Fib(N-2)并建立栈帧后,实际上 Fib(N-2)与Fib(N-1)用的是同一块栈帧空间。这是由于时间是累积的,而空间是可以重复利用的。

(2)非递归实现:

法一:迭代实现

<1>时间复杂度:O(n)

<2>空间复杂度:O(1)

法二:用三个变量来回计算

<1>时间复杂度:O(n)

<2>空间复杂度:O(1)

3.杨辉三角递归:第一列和正对角线上的数字全为1,其他数字为其上方和左上方的数字之和。

4.射击运动员打靶递归:

当前为第u个靶子,共有n个靶子,cnt为当前环数总和,sum是环数总和。

第u个靶子可以取的环数范围为0~10,若到达最后一个靶子后,当前环数总和与最后环数总和相等,则输出存储各靶子环数的数组path,否则退回最后一个靶子,重新从0~10取环数。以此类推。

5.全排列递归:总共有n个数字的位置,第u个位置, 用1~n的数字填充且不能重复,用w数组记录数字是否使用过,用path数组记录当前数字,由于填充的位置下标为0~n-1,所以当到达第n个位置时,输出path数组中存储的数字。以此类推,返回并采用当前位置未使用过的数字。

6.GRAY码递归:求num个n位Gray码,分治到只有1位Gray码的情况;将求解n位Gray码的问题划分成求解n-1位Gray码的问题,n位Gray码的前半部分最高位置0,其余位不变,n位Gray码的后半部分最高位置1,其余位由n-1位Gray码翻转而来。

五、实验中的问题及解决:(本次实验中在编程中碰到的错误等问题及解决方法)

1.递归与尾递归的区别:

(1)实现斐波那契数列的递归函数:

int fib (int n)

{

if (n <= 1)

        return n;

else

        return fib (n - 1) + fib (n - 2);

}

这个递归函数会不断地调用自己来计算斐波那契数列。这个实现在小规模的输入上是有效的,但当输入值较大时,递归的层数会变得很深,导致性能下降,并可能导致栈溢出的问题

(2)实现斐波那契数列的尾递归函数:

int fib (int n, int a = 0, int b = 1)

{

    if (n == 0)

        return a;

    else

        return fib(n - 1, b, a + b);

}

在这个尾递归函数中,递归调用位于函数的最后一步,并且使用新的参数直接取代了之前的参数。这样就避免了在递归调用之后还需要进行额外的计算或操作。这个方式下,递归调用并不会增加栈的深度,因此不会有栈溢出的风险。尾递归函数可以通过对参数的更新来实现迭代的效果,从而提高性能和减少内存的使用。

2.斐波那契数列三种方法的时空复杂度计算:

(1)递归:

<1>时间复杂度:

由于每次递归调用都会生成两个新的递归调用,且递归树是指数级的,因此时间复杂度为 O(2^n)。

<2>空间复杂度:

每个递归调用会在栈中创建一个新的函数帧,而递归栈的深度取决于输入值 n,因此空间复杂度为 O(n)。

(2)迭代:

<1>时间复杂度:

迭代方式的斐波那契数列计算仅需要一次循环遍历,每次迭代都在常数时间内完成。因此,时间复杂度为 O(n)。

<2>空间复杂度:

尽管数组大小为 100,但实际上只使用了数组中的前 n 个元素,其中 n 是输入的斐波那契数列位置。因此,在实际计算过程中,空间复杂度是与 n 相关的。不过根据该代码实现中 n 的输入上限为 100,因此空间复杂度可以视为常数级别。

(3)三变量:

<1>时间复杂度:

迭代方式的斐波那契数列计算使用了一个循环来计算第 n 位的数值。循环的次数与输入值 n 相关,因此时间复杂度为 O(n)。

<2>空间复杂度:

在迭代方式中,只使用了几个整数变量来保存中间结果和计算,没有使用数组或其他额外的数据结构。因此,空间复杂度为 O(1),即常数级别的空间复杂度。

六、实验总结和思考:(填写收获和体会,分析成功或失败的原因) 

1.什么是递归:

递归函数直接调用自己或通过一系列调用语句间接调用自己。

有些问题使用传统的迭代算法是很难求解甚至无解的,而使用递归却可以很容易的解决。比如汉诺塔问题。但递归的使用也是有它的劣势的,因为它要进行多层函数调用,所以会消耗很多堆栈空间和函数调用时间。

2.递归问题的两个基本条件:

(1)大问题可以分解为具有相同形式、相同解法的小问题

(2)递归存在明确的终止条件

3.递归与显式用栈实现的方法比较:

递归的优点是代码简洁易懂,可以自然地表达问题的逻辑。然而,递归也有一些缺点。由于每次函数调用都需要保存现场,递归在大规模问题上耗费的空间很大。此外,递归的执行过程中会有多次函数调用和返回,这在一些情况下可能导致性能问题。

显式用栈,指的是显式地使用栈数据结构来解决问题。通过手动操作栈的入栈和出栈操作,可以模拟递归的行为。相比于递归,使用显式栈的方式可以对栈中的数据和状态进行更加灵活的控制。同时,显式用栈不会出现递归过程中的函数调用和返回操作,因此性能会相对较好。然而,显式使用栈需要手动维护栈的状态和数据,这可能会增加编码的复杂度。

在空间要求较高、问题规模较大、性能要求较高的情况下,显式用栈会更适合。而在问题逻辑较为简单、代码易读易写的情况下,递归会更方便使用。

4.什么是栈溢出:

当一个函数被调用时,程序会在栈上为其分配一块内存区域,用于存储函数的参数、局部变量和返回地址等信息。每个函数调用都会在栈上创建一个新的栈帧,以保存这些信息。当函数执行完毕后,对应的栈帧会被销毁,释放对应的内存。

当递归函数无限递归调用,或者函数内部使用大量的局部变量导致栈空间无法容纳时,就会发生栈溢出。此时,栈空间被耗尽,无法继续为新的函数调用创建栈帧,导致程序异常终止。

5.使用栈缓解栈溢出的问题:

(1)优化递归算法:递归算法是常见的导致栈溢出的原因之一。可以考虑优化递归算法,将其转换为迭代算法或者使用尾递归优化以减少函数调用的层次

(2)减少局部变量的使用:局部变量会占用栈空间。如果函数中使用的局部变量较多,可以尝试减少或者合并局部变量的使用,以降低栈的负担。

(3)动态内存分配:将一部分数据从栈空间转移到堆空间,通过动态内存分配(如使用new或malloc函数)来存储大量数据或者较大的数据结构,以减轻对栈空间的占用。

(4)使用循环代替递归:如果递归不是必要的,可以考虑使用循环来替代递归调用,这样可以避免递归过深而导致的栈溢出问题。

  • 34
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值