文章目录

  • 前言
  • 递归和数学归纳法
  • 递归三步走
  • 递归的注意点
    • 避免栈溢出
    • 避免重复运算
  • 题目
    • 斐波那契数
    • 反转链表


前言

  递归(Recursion):指的是一种通过重复将原问题分解为同类的子问题而解决的方法。在绝大数编程语言中,可以通过在函数中再次调用函数自身的方式来实现递归。
  举个简单的例子来了解一下递归算法。比如阶乘的计算方法在数学上的定义为:

def fact(n):if n == 0:return 1return n * fact(n - 1)

以 n=6为例,上述代码中阶乘函数的计算过程如下:

fact(6)
= 6 * fact(5)
= 6 * (5 * fact(4))
= 6 * (5 * (4 * fact(3)))
= 6 * (5 * (4 * (3 * fact(2))))
= 6 * (5 * (4 * (3 * (2 * fact(1)))))
= 6 * (5 * (4 * (3 * (2 * (1 * fact(0))))))
= 6 * (5 * (4 * (3 * (2 * (1 * 1)))))
= 6 * (5 * (4 * (3 * (2 * 1))))
= 6 * (5 * (4 * (3 * 2)))
= 6 * (5 * (4 * 6))
= 6 * (5 * 24)
= 6 * 120
= 720

  根据上面的描述,我们可以把阶乘函数的递归计算过程分为两个部分:

  1. 先逐层向下调用自身,直到达到结束条件(即n==0。
  2. 然后再向上逐层返回结果,直到返回原问题的解(即返回 fact(6)==720
    这两个部分也可以叫做「递推过程」和「回归过程」,如下面两幅图所示:

  如上面所说,我们可以把「递归」分为两个部分:「递推过程」和「回归过程」。
  • 递推过程:指的是将原问题一层一层地分解为与原问题形式相同、规模更小的子问题,直到达到结束条件时停止,此时返回最底层子问题的解。
  • 回归过程:指的是从最底层子问题的解开始,逆向逐一回归,最终达到递推开始时的原问题,返回原问题的解。
  「递推过程」和「回归过程」是递归算法的精髓。从这个角度来理解递归,递归的基本思想就是: 把规模大的问题不断分解为子问题来解决。
  同时,因为解决原问题和不同规模的小问题往往使用的是相同的方法,所以就产生了函数调用函数自身的情况,这也是递归的定义所在。

递归和数学归纳法

  递归的数学模型其实就是「数学归纳法」。这里简单复习一下数学归纳法的证明步骤:
  1.证明当n=b (b 为基本情况,通常为 0 或者 1)时,命题成立。
  2.证明n>b 时,假设n=k时命题成立,那么可以推导出n=k+1时命题成立;这一步不是直接证明的,而是先假设n=k时命题成立,利用这个条件,可以推论出n=k+1时命题成立。
  通过以上两步证明,就可以说:当n>=b 时,命题都成立。
  我们可以从「数学归纳法」的角度来解释递归:
  1.递归终止条件:数学归纳法第一步中的n=b,可以直接得出结果。
  2.递推过程:数学归纳法第二步中的假设部分(假设n=k时命题成立),也就是假设我们当前已经知道了n=k时的计算结果。
  3.回归过程:数学归纳法第二步中的推论部分,根据n=k推论出n=k+1)也就是根据下一层的结果,计算出上一层的结果。

递归三步走

  递归的基本思想就是: 把规模大的问题不断分解为子问题来解决。 那么,在写递归的时候,我们可以按照这个思想来书写递归,具体步骤如下:
  1. 写出递推公式:找到将原问题分解为子问题的规律,并且根据规律写出递推公式。
  2. 明确终止条件:推敲出递归的终止条件,以及递归终止时的处理方法。
  3. 将递推公式和终止条件翻译成代码:
    1. 定义递归函数(明确函数意义、传入参数、返回结果等)。
    2. 书写递归主体(提取重复的逻辑,缩小问题规模)。
    3. 明确递归终止条件(给出递归终止条件,以及递归终止时的处理方法)。

递归的注意点

避免栈溢出

  在程序执行中,递归是利用堆栈来实现的。每一次递推都需要一个栈空间来保存调用记录,每当进入一次函数调用,栈空间就会加一层栈帧。每一次回归,栈空间就会减一层栈帧。由于系统中的栈空间大小不是无限的,所以,如果递归调用的次数过多,会导致栈空间溢出。
  为了避免栈溢出,我们可以在代码中限制递归调用的最大深度来解决问题。当递归调用超过一定深度时(比如 100)之后,不再进行递归,而是直接返回报错。
  当然这种做法并不能完全避免栈溢出,也无法完全解决问题,因为系统允许的最大递归深度跟当前剩余的占空间有关,事先无法计算。
  如果使用递归算法实在无法解决问题,我们可以考虑将递归算法变为非递归算法(即递推算法)来解决栈溢出的问题。

避免重复运算

  在使用递归算法时,还可能会出现重复运算的问题。
  比如斐波那契数列的定义是

  从图中可以看出:想要计算 f(5),需要先计算 f(3) 和 f(4),而在计算 f(4) 时还需要计算 f(3),这样f(3) 就进行了多次计算。同理 f(0)、f(1)、f(2) 都进行了多次计算,就导致了重复计算问题。
  为了避免重复计算,我们可以使用一个缓存(哈希表、集合或数组)来保存已经求解过的 f(k) 的结果,这也是动态规划算法中的做法。当递归调用用到f(k) 时,先查看一下之前是否已经计算过结果,如果已经计算过,则直接从缓存中取值返回,而不用再递推下去,这样就避免了重复计算问题。

题目

斐波那契数

  递推三步走策略,写出对应的递归代码。
  1写出递推公式:f(n)=f(n-1)+f(n-2)
  2 明确终止条件:f(0)=0,f(1)=1
  3 翻译为递归代码:
    1. 定义递归函数:fib(self, n) 表示输入参数为问题的规模 n,返回结果为第 n 个斐波那契数。
    2. 书写递归主体:return self.fib(n - 1) + self.fib(n - 2)。
    明确递归终止条件:
    1. if n == 0: return 0
    2. if n == 1: return 1

class Solution:def fib(self, n: int) -> int:if n == 0:return 0if n == 1:return 1return self.fib(n - 1) + self.fib(n - 2)

反转链表

  描述:给定一个单链表的头节点 head。
  要求:将该单链表进行反转。可以迭代或递归地反转链表。
示例
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
解释:
翻转前 1->2->3->4->5->NULL
反转后 5->4->3->2->1->NULL

具体做法如下:

  1. 首先定义递归函数含义为:将链表反转,并返回反转后的头节点。
  2. 然后从 head.next 的位置开始调用递归函数,即将 head.next 为头节点的链表进行反转,并返回该链表的头节点。
  3. 递归到链表的最后一个节点,将其作为最终的头节点,即为 new_head。
  4. 在每次递归函数返回的过程中,改变 head 和 head.next 的指向关系。也就是将 head.next 的next指针先指向当前节点 head,即 head.next.next = head 。
  5. 然后让当前节点 head 的 next 指针指向 None,从而实现从链表尾部开始的局部反转。
  6. 当递归从末尾开始顺着递归栈的退出,从而将整个链表进行反转。
  7. 最后返回反转后的链表头节点 new_head。
1.class Solution:
2.    def reverseList(self, head: ListNode) -> ListNode:
3.        if head == None or head.next == None:
4.            return head
5.        new_head = self.reverseList(head.next)
6.        head.next.next = head
7.        head.next = None
8.        return new_head

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TVFEMD-CPO-TCN-BiLSTM多输入单输出模型

47-TVFEMD-CPO-TCN-BiLSTM多输入单输出模型 适合单变量,多变量时间序列预测模型(可改进,加入各种优化算法) 时变滤波的经验模态分解TVFEMD时域卷积TCN双向长短期记忆网络BiLSTM时间序列预测模型 另外以及有 TCN-BILSTM …

深入浅出Node.js中间件机制

我们用一个实际的例子来看看中间件是如何运作的。假设我们有一个非常简单的Express应用,它只有两个中间件函数: const express require(express); const app express();app.use((req, res, next) > {console.log(第一个中间件);next(); });app.use…

Vue-15-前端框架Vue之应用基础编程式路由导航

文章目录 1 RouterLink的replace属性1.1 App.vue1.2 应用效果2 编程式路由导航2.1 场景一Home.vue2.2 场景二News.vue3 路由重定向3.1 index.ts3.2 Detail.vue3.3 About.vue1 RouterLink的replace属性 路由每次跳转都有记录,默认是push,可以改为replace。 RouterLink支持两…

android14 设置下连续点击5次Settings标题跳转到拨号界面

部分项目隐藏了拨号器,但开发者需要间距跳转到拨号界面 设置一级界面: packages/apps/Settings/src/com/android/settings/homepage/SettingsHomepageActivity.java 通过dispatchTouchEvent方法先获取Settings标题的区域X,Y数据。 import java.util.Set…

MP分页和连表常用写法

1. 分页查询 方案一&#xff1a;MyBatis XML MyBatis 内置的使用方式&#xff0c;步骤如下&#xff1a; ① 创建 AdminUserMapper.xml 文件&#xff0c;编写两个 SQL 查询语句&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <!DOCTYPE m…

使用 Spring AI Alibaba构建 AI Code Review 应用

很早的时候就想着用AI来做Code Review&#xff0c;最近也看到了一些不错的实现&#xff0c;但是没有一个使用Java来构建的&#xff0c;看的比较费劲&#xff0c;虽然说语言只是一种工具&#xff0c;但是还是想用Java重新写一遍&#xff0c;正好最近Spring AI Alibaba出了正式版…

力扣1590. 使数组和能被 P 整除

这一题的难点在于模运算&#xff0c;对模运算足够了解&#xff0c;对式子进行变换就很容易得到结果&#xff0c;本质上还是一道前缀和哈希表的题 这里重点讲一下模运算。 常见的模运算的用法 (a-b)%k0等价于 a%kb%k 而在这一题中由于多了一个len&#xff0c;&#xff08;数组的…

FPGA内部资源介绍

FPGA内部资源介绍 目录 逻辑资源块LUT&#xff08;查找表&#xff09;加法器寄存器MUX&#xff08;复用器&#xff09;时钟网络资源 全局时钟网络资源区域时钟网络资源IO时钟网络资源 时钟处理单元BLOCK RAMDSP布线资源接口资源 用户IO资源专用高速接口资源 总结 1. 逻辑资源…

CSS 列表

CSS 列表 引言 CSS 列表是网页设计中常用的一种布局方式&#xff0c;它能够帮助我们以更灵活、更美观的方式展示数据。本文将详细介绍 CSS 列表的创建、样式设置以及常用技巧&#xff0c;帮助您更好地掌握这一重要技能。 CSS 列表概述 CSS 列表主要包括两种类型&#xff1a…

spring中的@Cacheable缓存

1. 使用方法 在方法上面加上注解Cacheable&#xff0c; OverrideCacheable(cacheNames "userCache", key "#id")public User getUserById(Long id) {System.out.println("查询数据库了");return getById(id);}如果你的项目中引入了&#xff…

Node.js特训专栏-实战进阶:9.MySQL连接池配置与优化

🔥 欢迎来到 Node.js 实战专栏!在这里,每一行代码都是解锁高性能应用的钥匙,让我们一起开启 Node.js 的奇妙开发之旅! Node.js 特训专栏主页 专栏内容规划详情 MySQL连接池配置与优化:提升数据库交互性能的关键 一、MySQL连接池基础概念 1.1 什么是连接池? 连接池是…

【innovus基础】- 如何手动画线?

后端实现的过程就是将逻辑连线变为物理的金属连线的过程。 1、打开Pin shape的Visible 和 Selected开关&#xff0c;使其可见并可选 2、选中想要画线的IOCell 3、鼠标选中对应的pin 4、使用dbGet 获取此pin脚逻辑连线net的名字&#xff1b; dbGet selected.net.name 5、使用画…

element-plus限制日期可选范围(这里以7天为例)

element-plus日期范围限制功能实现逻辑 1. 需求&#xff1a;通过限制时间的可选范围减少请求的数据量 2. 实现效果&#xff1a; 日期选择器做限制 3. 代码逻辑&#xff1a; 思路&#xff1a;通过calendar-change获取开始日期&#xff0c;然后通过disabled-date禁用不满足条件…

机器学习2-梯度下降与反向传播

损失函数 与 平均方差函数 傻傻分不清 损失函数是概念&#xff1b;平均方差函数是具体的实现 损失函数&#xff08;如均方误差 MSE&#xff09;用于衡量模型预测值与真实值之间的差距。损失越小&#xff0c;说明模型对当前数据的拟合越好。 但模型并非拟合度越高越好&#xf…

安全生产风险管控平台:企业安全管理的智能化解决方案

在工业生产、建筑施工、能源化工等领域&#xff0c;安全生产是企业可持续发展的基石。然而&#xff0c;传统安全管理模式依赖人工巡检、纸质记录和事后处理&#xff0c;难以满足现代化企业的高效风险管控需求。安全生产风险管控平台应运而生&#xff0c;它利用物联网、大数据、…

如何保证数据库与 Redis 缓存的一致性?

在现代互联网应用中&#xff0c;Redis 缓存几乎是性能优化的标配。但在使用过程中&#xff0c;一个绕不过去的问题就是&#xff1a; 如何保证 Redis 缓存与数据库之间的数据一致性&#xff1f; 特别是在高并发场景下&#xff0c;读写操作错位可能导致缓存中出现脏数据&#xff…

现代 JavaScript (ES6+) 入门到实战(三):字符串与对象的魔法升级—模板字符串/结构赋值/展开运算符

在前两篇&#xff0c;我们升级了变量和函数。今天&#xff0c;我们要给 JavaScript 中最常用的两种数据类型——字符串&#xff08;String&#xff09;和对象&#xff08;Object&#xff09;——装备上 ES6 带来的强大魔法。 准备好告别丑陋的 号拼接和重复的对象属性赋值了吗…

GitLab 备份恢复与配置迁移详尽教程(实战版)

&#x1f6e0; GitLab 备份恢复与配置迁移详尽教程&#xff08;实战版&#xff09; &#x1f9f1; 一、环境准备 1.1 检查版本一致性 恢复目标机 GitLab 版本必须与备份文件所用版本一致或兼容&#xff08;推荐相同版本&#xff09; 查看当前 GitLab 版本&#xff1a; sudo g…

英飞凌高性能BMS解决方案助力汽车电动化

随着电动汽车越来越被大众接受&#xff0c;车辆电气化、智能化程度越来越高&#xff0c;如何提高电动汽车的续航里程&#xff0c;同时保障车辆安全可靠持久运行是当前最主要的技术难题之一。而先进的电池管理系统 (BMS)有助于克服电动汽车广泛普及的关键障碍&#xff1a;续航里…

react + ant-design实现数字对比动画效果:当新获取的数字比之前展示的数字多或少2时,显示“+2”或“-2”的动画效果

react ant-design实现数字对比动画效果&#xff1a;当新获取的数字比之前展示的数字多或少2时&#xff0c;显示“2”或“-2”的动画效果 1. 创建独立的 AnimatedValue 组件 // components/AnimatedValue/index.jsx import React, { useState, useEffect, useRef } from reac…