基础算法--递归算法【难点、重点】

news2024/11/18 0:38:36


今天我们即将要开始讲解算法中第一块儿难啃地骨头--递归了,相信有不少小伙伴都因递归而迷惑过,本文就来给大家详细的讲解一下递归到底是什么东西。让你也能瞬间将他打回原形。


递归的理解

在学习递归之前,我们先理解递归。什么是递归呢?从名字上看我们可以想到递进+回归两个词,那么我们实际的递归也有这么两个过程,只不过有些人将它称为前进状态后退状态,也有人将后者称为回溯地过程。不过我们后面会有单独的一个讲解回溯的章节,所以我在这里就按我的理解来称呼了。(称呼什么的无所谓,主要是你能理解这个过程)。从另一个官方的角度来解释递归,就是一种解决问题的方法,它通过函数调用自身来实现。

递归的故事

博主在网上搜寻资料时,看到大多数都是通过“从前有座山,山里有座庙,庙里有个老和尚在给小和尚讲故事,讲的是什么呢?讲的是"从前有座山,山里有座庙,庙里有个老和尚在给小和尚讲故事,讲的是什么呢?讲的是"从前有座山,山里有座庙,庙里有个老和尚在给小和尚讲故事,讲的是什么呢?讲的是讲完了,睡觉"讲完了,睡觉"讲完了,睡觉”这个例子来引入递归的。我觉得这个故事对递归的说明还不是那么地贴切。在这里我给出另一个生动的故事来形象化这个概念:

一个小孩在一个高大的山上。他想知道山的另一边是什么样子,于是他决定向下走。(问题引入:功能需求)

他问他遇到的每一个人:“山的另一边有什么?”其中一个人告诉他:“这里也有个小孩,他知道山的另一边更清楚。”于是这个小孩找到下一个小孩,问:“你知道山的另一边有什么吗?”这个小孩又回答:“等一下,我再去问我的朋友。”(解决方法:拆解问题,层层递推)

这样,孩子们通过询问,把问题层层递归地传递下去,直到某个小孩找到一个真正到了山的另一边的人,得到了答案。(结束条件:基线状态)

然后这个小孩从别人口中得知山的另一边,他就回头告诉问他的那个人山的另一边是什么,依次回退。(问题解决,依次回归)

这个过程就是递归:通过拆解问题,直到找到简单和直接的解决方案。

故事图解:

代码:

string kid(vector<string>& kids, int i){
    if(kids[i]=="我知道了")
        return kids[i];//如果第i个小孩知道了,就将这个答案返回去
    //else,否则执行下面的代码
    return kid(kids,i+1);//在这群孩子中,返回下一个孩子知道的答案,下一个不知道的话,会一直往下问,直到有个孩子知道答案,就开始将答案返回了。
};

string kid(vector<string>& kids, int i){
    if(kids[i]=="我知道了") return kids[i];
    string ans=kid(kids,i+1);
    return ans;
}

代码图解 :

递归的代码示例

让我们来看一个简单的代码示例——计算阶乘。阶乘是一个经典的递归问题,可以用递归来优雅地解决。

#define Long long long
Long function(int n)
{
    if(n==0)return 1;//确定结束条件
    return n*function(n-1);//递归调用
}

在这个例子中:
功能:计算一个数的阶乘。
结束条件:当 `n` 等于 0 时,返回 1(因为 0! = 1)。
等价关系式:其他情况下,`n! = n * (n-1)!`。这个表达式使得我们能把问题分解为更小的部分,最终能解决。

在这里,假设main()是主调函数 f(int a)就作为main()的被调函数。main()调用f(6),f(6)调用f(5),f(5)调用f(4),f(4)调用f(3),f(3)调用f(2),f(2)调用f(1);而f(1)达成了结束条件,开始返回f(1)的值,在f(2)中f(1)调用完毕,就返回f(2)的值,依次回归。直至f(6)返回,回到main()主函数。 

递归的优缺点

一、优点

1. 优雅性:递归允许我们以一种简洁和优雅的方式编写代码,特别适合解决具有递推关系的问题。
2. 清晰性:通常情况下,使用递归可以使代码更易读。

二、缺点

1. 栈溢出:递归调用会消耗栈空间,如果递归深度过大,可能会导致栈溢出。
2. 性能问题:在某些情况下,递归的性能不如迭代算法,尤其是没有使用尾递归优化或记忆化的情况下。

优化递归

1. 尾递归:尾递归指的是递归调用是函数的最后一步,可以通过编译器优化避免堆栈增长,从而减少栈溢出的风险。
2. 记忆化:记忆化是一种优化技术,保存已计算的结果,以避免重复计算。这在处理重叠子问题时尤为有效,例如在求 Fibonacci 数列时。

一、尾递归

二、记忆化

1201:菲波那契数列
时间限制: 1000 ms         内存限制: 65536 KB

【题目描述】

菲波那契数列是指这样的数列: 数列的第一个和第二个数都为1,接下来每个数都等于前面2个数之和。

给出一个正整数a,要求菲波那契数列中第a个数是多少。

【输入】

第1行是测试数据的组数n,后面跟着n行输入。每组测试数据占1行,包括一个正整数a(1<=a<=20)。

【输出】

输出有n行,每行输出对应一个输入。输出应是一个正整数,为菲波那契数列中第a个数的大小。

正常来说,这道题很简单,写一个计算斐波那契第i位的递归函数就可以了。

#include <bits/stdc++.h>
using namespace std;
#define Long long long

Long func(int n) {
	if (n == 1 || n == 2)return 1;//斐波那契数列第一二项是必然已知的。所以这就是我们的基线条件
	return func(n - 1) + func(n - 2);//斐波那契的等价关系式也是已知的,前两项之和
}
int main() {
	int n; cin >> n;
	for (int i = 0; i < n; i++) {
		int x; cin >> x;
		cout << func(x) << endl;
	}
	return 0;
}

 

对于斐波那契第5个数,需要推到出来,我们正常的步骤是17步。 (一会跟后面对比一下)

对于本题而言,需要输出n组数据,每次打印第i个数据斐波那契数列的值。假如我们第一组数据是100,第二组数据是99,我们每次都调用递归,是不是感觉它很烦,明明第99位斐波那契数我已经推到出来过了,我还要重新推导。所以,我们需要采用记忆化的方法,来解决这个问题。

#include <bits/stdc++.h>
using namespace std;
#define Long long long

Long fib[25]={0,1,1};
Long fuc(int i){
    if(fib[i]) return fib[i];
    fib[i]=func(i-1)+func(i-2);
    return fib[i];
}

int main() {
	int n; cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> x;
        cout<<fuc(x)<<endl;
	}
	return 0;
}

我们定义一个全局数组(此处又称记忆数组)fib,用fib[i]来表示第i个斐波那契数的值。初始我们将fib[1]和fib[2]的值设置为1。这次再进行斐波那契数列的递归调用时,我们每推导出一个fib[i]都先给他存起来,之后求func(i)时我们就可以直接拿fib[i]这个数组的值了。这就是记忆化的过程。

我们可以明显的看出来,少了四步,然而这只是fib5,当求的位数越大时,调用栈的次数就会少,记忆化的优势就越明显。( 这个结构类似于二叉搜索树)。由于左面的子树的存储的位数比右面都要靠后,当我一条线走完左面的子树时,右面的子树全部都被存储到了记忆化数组中去,之后就不用再依次往下递归了,从这个角度来说,树的深度越大,记忆化的好处越明显。

递归的知识串烧

一、递归、递推、迭代

1、递推:其对应英文应该是recurrence relation(Inductive),递推关系。什么是递推关系呢?从数学角度,递推关系往往可以用数学公式来表示。比如,高中学的等差数列等比数列,a1=1, an=再比如fibonacci,Fn = Fn-1+Fn-2.递推可以理解是数学上的概念从已知到未知, 从1 往 n推(未知)。递进 依次 推算。

2、递归:对应英文recursion,这是一个计算机科学里的概念,其定义为函数自己调用自己。计算机科学里除了递归,还有一个是迭代,它们和递推三者的关系,可以理解为:

在编程里,递推关系可以通过递归或者迭代来实现,但是递归和迭代又不仅仅只能用来实现递推关系。从未知到已知 Recursive是从n(未知)往1推, 再层层返回归纳

3、迭代(辗转) --Iterative ,不断将结果当做变量带入,就叫迭代

总结
1,从程序上看,递归表现为自己调用自己,迭代则没有这样的形式。
2,递归是从问题的最终目标出发,逐渐将复杂问题化为简单问题,最终求得问题
是逆向的。迭代是从简单问题出发,一步步的向前发展,最终求得问题。是正向的。
3,递归中,问题的n要求是计算之前就知道的,而迭代可以在计算中确定,不要求计算前就知道n。
4,一般来说,递推的效率高于递归(当然是递推可以计算的情况下)

二、递归与栈

在C++中,递归和栈有着密切的关系。以下是它们之间的一些关键联系:

  1. 调用栈

    • 当一个函数被调用时,程序会将该函数的执行状态(包括参数、局部变量和返回地址)压入调用栈中。递归调用函数时,每次调用都会在栈上创建一个新的帧,包含该调用的上下文信息。
  2. 递归函数

    • 递归函数是直接或间接调用自身的函数。每个递归调用都会把当前函数的信息推入栈中,直到达到基准条件(即递归停止条件),这时栈开始逐层回退,逐步返回结果。
  3. 栈溢出

    • 由于每次递归调用都会占用栈空间,如果递归深度过大,可能会导致栈溢出(stack overflow),从而使程序崩溃。这通常会发生在递归深度过大或者没有合适的基准条件时。
  4. 栈的使用

    • 在递归过程中,每个调用都有其独立的局部变量和状态,这些信息都存储在栈中。因此,栈为递归提供了存储上下文的机制。
  5. 尾递归优化

    • 在某些编程语言中,如果一个递归调用是函数的最后一步(尾递归),编译器可以优化这个调用,以避免增加栈深度。不过,C++标准并不保证支持尾递归优化,因此在C++中使用尾递归仍然需要谨慎。

综上所述,递归依赖于栈来管理函数调用的状态,理解二者的关系能够帮助我们更有效地使用递归,同时避免可能的问题。

注意事项

尽管递归是一种强大的工具,有时并不是所有问题都适合用递归来解决。在选择使用递归时,应考虑问题的特性,确保其能提升代码的可读性和可维护性。

结语

递归是一种解决问题的策略,通过函数自我调用来分解问题。它需要明确的功能、结束条件和等价关系式,尽管递归有其优点和缺点,但在适当的情况下,它能够提供简洁和优雅的解决方案。


感谢大家观看!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2184416.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【论文阅读】MRFS: Mutually Reinforcing Image Fusion and Segmentation(CVPR2024)

MRFS: Mutually Reinforcing Image Fusion and Segmentation&#xff08;CVPR2024&#xff09; 背景和动机 1.特征中和缺点和低光信息丢失。 红外和可见光图像通常在强度和梯度分布方面表现出显着差异。在这种情况下&#xff0c;传统的固定比例的无监督损失可能会削弱有价值…

NASA:ATLAS/ICESat-2 L3B 每日和每月网格极地海面高度异常 V003

目录 简介 摘要 代码 引用 网址推荐 0代码在线构建地图应用 机器学习 ATLAS/ICESat-2 L3B Daily and Monthly Gridded Polar Sea Surface Height Anomaly V003 ATLAS/ICESat-2 L3B 每日和每月网格极地海面高度异常 V003 简介 ATLAS/ICESat-2 L3B Daily and Monthly G…

Gamisodes在Sui上推出动画版《神探加杰特》

没错&#xff0c;大家喜爱的卡通角色《神探加杰特》将以全新可玩形式登陆Sui区块链&#xff0c;这要归功于Gamisodes。通过其独特技术&#xff0c;Gamisodes正将《神探加杰特》重新制作为互动式剧集&#xff0c;并在Sui上重现这一经典系列。 Gamisodes平台推出了真正互动的《神…

高精度四则运算专题

高精度X高精度加法、减法、乘法 高精度X普通精度 乘法 高精度X普通精度 除法 目录 高精度X高精度 加法 题目 要点 代码 高精度X高精度 减法 题目 要点 代码 高精度X普通精度 乘法 题目 要点 代码 高精度X高精度 乘法 要点 代码 高精度X普通精度 除法 题目 要点 代…

GESP C++四级样题卷

&#xff08;满分&#xff1a;100 分 考试时间&#xff1a;90 分钟&#xff09; PDF试卷及答案回复:GESPC20234 一、单选题&#xff08;每题 2 分&#xff0c;共 30 分&#xff09; 1.在 C中&#xff0c;指针变量的大小&#xff08;单位&#xff1a;字节&#xff09;是&#…

计算机网络-系分(5)

目录 计算机网络 DNS解析 DHCP动态主机配置协议 网络规划与设计 层次化网络设计 网络冗余设计 综合布线系统 1. 双栈技术 2. 隧道技术 3. 协议转换技术 其他网络技术 DAS&#xff08;Direct Attached Storage&#xff0c;直连存储&#xff09; NAS&#xff08;Net…

《Windows PE》3.2.4节表

节表由多个节表项&#xff08;IMAGE_SECTION_ HEADER&#xff09;组成&#xff0c;每个节表项&#xff08;40个字节&#xff09;记录了 PE中与某个特定的节有关的信息&#xff0c;如节的属性、节 的大小、在文件和内存中的起始位置等。节表中节的数量由字段IMAGE_FILE_HEADER. …

vulnhub-Matrix 1靶机

vulnhub&#xff1a;https://www.vulnhub.com/entry/matrix-1,259/ 导入靶机&#xff0c;扫描IP 靶机在192.168.81.6&#xff0c;扫描端口 存在三个端口&#xff0c;有两个都是http服务&#xff0c;访问 80端口的网页没什么信息&#xff0c;31337的网页元素里有注释 ZWNobyAi…

Raft 协议解读:简化分布式一致性

文章目录 1. 分布式系统与一致性问题1.1 什么是分布式系统1.2 一致性在分布式系统中的重要性1.3 分布式一致性挑战1.4 现有一致性协议1.5 Raft 的设计目标 2. Raft 协议的背景与介绍2.1 Raft 协议的诞生背景2.2 什么是 Raft2.3 Raft 解决的一致性问题2.4 Raft 的设计原则2.5 Pa…

Oracle 数据库安装及配置

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

PCL MLS上采样(基于法向量)

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 可视化原始点云和上采样后的点云 2.1.2 MLS 上采样 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接&#xff1a; PCL点云算法与项目实战案例汇总&#xff0…

Redis-预热雪崩击穿穿透

预热雪崩穿透击穿 缓存预热 缓存雪崩 有这两种原因 redis key 永不过期or过期时间错开redis 缓存集群实现高可用 主从哨兵Redis Cluster开启redis持久化aof&#xff0c;rdb&#xff0c;尽快恢复集群 多缓存结合预防雪崩&#xff1a;本地缓存 ehcache redis 缓存服务降级&…

iPhone/iPad技巧:如何解锁锁定的 iPhone 或 iPad

“在我更新 iPhone 上的软件后&#xff0c;最近我遇到了iPhone 被锁定到所有者的消息&#xff0c;该如何解决&#xff1f;” 根据我们的研究&#xff0c;许多用户在 iOS 18 更新或恢复出厂设置后都会遇到同样的问题。只要出现问题&#xff0c;您就无法使用 iPhone 或 第 1 部分…

jenkins微服务

如果vim进去某个文件里&#xff0c;可以按键盘的向下键查阅其它部分 记得每天备份虚拟机的项目 一.在linux安装jenkins 1.上传文件 我们采用安装包的方式安装。 先用SShclient在/usr/local/下创建jenkins文件夹&#xff0c;然后向其中导入两个包 2.安装jenkins 再在控制…

「STL::queue」标准库适配器:priority_queue(优先队列)介绍|自定义比较运算(C++)

目录 概述 创建销毁 内部理解 构造析构 自定义比较 赋值重构 数据访问 内存管理 数据控制 Tips 概述 priority_queue 是一种C标准模板库STL中定义的一种序列容器&#xff0c;它允许你在运行时动态地进行堆操作。 priority_queue 可以自动管理内存&#xff0c;这意味…

使用阿里云试用资源快速部署web应用-dofaker为例

本文介绍使用阿里云的试用资源部署dofaker的方法&#xff0c;本教程主要作学习在阿里云部署web应用之用&#xff0c;部署好应用之后&#xff0c;可以在任何地点通过公网ip访问web应用。 一、创建云主机 登录阿里云账户之后&#xff0c;点击控制台&#xff1a; 点击云服务器EC…

基于SSM的大学生心理素质测评及咨询平台系统设计与实现(源码+定制+讲解)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

vscode中配置python虚拟环境

python虚拟环境作用 Python虚拟环境允许你为每个独立的项目创建一个隔离的环境&#xff0c;这样每个项目都可以拥有自己的一套Python安装包和依赖&#xff0c;不会互相影响。实际使用中&#xff0c;可以在vscode或pycharm中使用虚拟环境。 1.创建虚拟环境的方法&#xff1a; …

天呐!关于PyCharm你竟然一无所知?

PyCharm 是一种专为 Python 开发而设计的集成开发环境&#xff08;IDE&#xff09;&#xff0c;由 JetBrains 开发。 以下是 PyCharm 的一些主要特点和常见的使用方法&#xff1a; 特点&#xff1a; 智能代码编辑&#xff1a;具有智能代码补全、语法检查、代码重构等功能&…

HISTCITE分析进阶

不可否认histcite是一个很好的文献分析的工具,他能很好的找到最重要的那几篇文章,同时也能找到研究的发文趋势、研究机构和著名的研究学者等。但是它是一个很老的软件,因而很多东西都没能跟上下载的分析。我在使用过程中,尝试做一些改变使其更好用,同时也做一些记录。 1.…