数据结构与算法之美 | 递归(Recursion)

news2024/11/29 20:59:44

什么叫做递归?

  • 递归:去的过程叫“递”,回来的过程叫“归”

递归的三个条件

  • 条件一:一个问题的解可以分解为几个子问题的解

  • 条件二:这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样

  • 条件三:存在递归终止条件

写递归的正确姿势

  • 关键点:写出递推公式,找到终止条件

    写递归代码的关键就是找到如何将大问题分解为小问题的规律,并且基于此写出递推公式,然后再推敲终止条件,最后将递推公式和终止条件翻译成代码

  • 只要遇到递归,我们就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤

假如有 n 个台阶,每次你可以跨 1 个台阶或者 2 个台阶,请问走这 n 个台阶有多少种走法?

假设 f ( n ) f(n) f(n) 表示走 n n n 个台阶的走法总数,那么有以下递推式:

f ( n ) = f ( n − 1 ) + f ( n − 2 ) f(n) = f(n-1) + f(n-2) f(n)=f(n1)+f(n2)

  • f ( n − 1 ) f(n-1) f(n1) 表示先走一步,剩下的 n − 1 n-1 n1 个台阶就变成了一个子问题,即 f ( n − 1 ) f(n-1) f(n1)

  • f ( n − 2 ) f(n-2) f(n2)表示先走两步,剩下的 n − 2 n-2 n2个台阶也变成了一个子问题,即 f ( n − 2 ) f(n-2) f(n2)。最后的结果就是这两个子问题的总和。

  • 边界条件是 f ( 1 ) = 1 f(1) = 1 f(1)=1 f ( 2 ) = 2 f(2) = 2 f(2)=2。因为只有一个台阶时只有一种走法,两个台阶时有两种走法,分别是一步一步走和直接跨两步。

用 Python 实现这个递推过程,可以写成如下代码:

def climbStairs(n):
    """
    计算爬楼梯的方案数
    :param n: 需要爬的楼梯数   
    :return: 爬楼梯的方案数
    """
    # 如果只有一级楼梯,只有一种爬法
    if n == 1:
        return 1
    # 如果有两级楼梯,有两种爬法
    if n == 2:
        return 2
    # 否则,根据递推公式计算爬楼梯的方案数
    return climbStairs(n-1) + climbStairs(n-2)

递归的副作用

堆栈溢出

问题原因:函数调用会使用栈来保存临时变量。每调用一个函数,都会将临时变量封装为栈帧压入内存栈,等函数执行完成返回时,才出栈。系统栈或者虚拟机栈空间一般都不大。如果递归求解的数据规模很大,调用层次很深,一直压入栈,就会有堆栈溢出的风险

解决方法:在代码中限制递归调用的最大深度

使用Python代码实现:

import sys
# 进行递归深度限制
sys.setrecursionlimit(50)

def climbStairs(n):
    """
    计算爬楼梯的方案数,避免堆栈溢出。
    :param n: 需要爬的楼梯数   
    :return: 爬楼梯的方案数
    """
    # 如果只有一级楼梯,只有一种爬法
    if n == 1:
        return 1
    # 如果有两级楼梯,有两种爬法
    if n == 2:
        return 2
    # 否则,根据递推公式计算爬楼梯的方案数
    return climbStairs(n-1) + climbStairs(n-2)

但这种做法并不能完全解决问题,因为最大允许的递归深度跟当前线程剩余的栈空间大小有关,事先无法计算。如果实时计算,代码过于复杂,就会影响代码的可读性。所以,如果最大深度比较小,比如 10、50,就可以用这种方法,否则这种方法并不是很实用。

重复计算

问题原因:在前述代码中,想要计算f(5),需要先计算 f(4) 和 f(3),而计算 f(4) 还需要计算 f(3),因此,f(3) 就被计算了很多次,这就是重复计算问题。

img

解决方法:可以通过一个数据结构(比如散列表)来保存已经求解过的 f(k)。当递归调用到 f(k) 时,先看下是否已经求解过了。如果是,则直接从散列表中取值返回,不需要重复计算,这样就能避免刚讲的问题了。

使用Python代码实现:

def climb_stairs(n, memo):
    """
    计算爬楼梯的方案数,避免重复计算。
    :param n: 需要爬的楼梯数
    :param memo: 用于保存已经计算过的结果的散列表
    :return: 爬楼梯的方案数
    """
    if n in memo:
        # 如果已经计算过,直接返回结果
        return memo[n]
    if n == 1:
        # 如果只有一级楼梯,只有一种爬法
        memo[n] = 1
    elif n == 2:
        # 如果有两级楼梯,有两种爬法
        memo[n] = 2
    else:
        # 否则,根据递推公式计算爬楼梯的方案数
        memo[n] = climb_stairs(n - 1, memo) + climb_stairs(n - 2, memo)
    # 返回计算结果
    return memo[n]


n = 10
memo = {}
print(climb_stairs(n, memo))

递归代码的调试方式

调试递归:

  1. 打印输出:在程序中添加打印输出语句,可以在程序执行时输出变量的值和程序执行的流程,以便于查找问题。
  2. 条件断点:使用条件断点来防止递归无限循环,并在满足特定条件时暂停程序的执行。

参考文献

  • 王争. “递归:如何用三行代码找到“最终推荐人”?” 极客时间. 2018

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

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

相关文章

CnOpenData全国养老机构数据

一、数据简介 养老机构指为老年人提供集中居住和照料服务的机构,县级以上地方人民政府民政部门负责本行政区域内养老机构的指导、监督和管理。其他有关部门依照职责分工对养老机构实施监督。 与其他服务不同的是,养老服务是一种全人、全员、全程服务,养老…

路径之谜 2016年国赛 深度优先搜索

目录 解题思路 AC代码: 题目描述 小明冒充 XX 星球的骑士,进入了一个奇怪的城堡。 城堡里边什么都没有,只有方形石头铺成的地面。 假设城堡地面是 nn 个方格。如下图所示。 按习俗,骑士要从西北角走到东南角。可以横向或纵向…

智能 CAN 总线/串口 RS-232485 协议转换器

能CAN/串口协议转换器LCNET Pro RS-232/485提供一路RS-485、一路RS-232和一路CAN通道,实现CAN与串口RS-485或RS-232之间的双向数据智能转换。每个通道独立隔离,每路通道采用金升阳电源模块和信号隔离芯片实现2500VDC电气隔离,电源输入防反设计…

NAT模式 LVS负载均衡群集部署

NAT模式 LVS负载均衡群集部署 一.部署共享存储(NFS服务器:192.168.80.102)1.关闭防火墙,查看是否有rpcbind和nfs-utils的包2.创建两个共享文件目录3.将共享路径及网段添加到/etc/exports中(设置为只可读)4.…

VulnHub项目:MONEYHEIST: CATCH US IF YOU CAN

靶机名称: MONEYHEIST: CATCH US IF YOU CAN 地址:MoneyHeist: Catch Us If You Can ~ VulnHub 这个系列是一部剧改编,还是挺好看的,大家有兴趣可以去看看! 废话不多说,直接上图开始! 渗透…

(单调栈) 496. 下一个更大元素 I——【Leetcode每日一题】

❓496. 下一个更大元素 I 难度:简单 nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。 给你两个 没有重复元素 的数组 nums1 和 nums2 ,下标从 0 开始计数,其中 nums1 是 nums2 的子集。 对…

直播美颜技术:视频美颜sdk的快速集成与开发实践

视频美颜sdk则是直播美颜技术的重要组成部分,它可以帮助开发者快速集成美颜功能,实现直播美颜。目前已经被广大平台、主播、平台用户所应用,在近几年甚至成了一个极其热门的讨论话题,毕竟它与人们的日常拍摄生活息息相关。 一、视…

重磅:百亿人工心脏赛道再添新玩家,行业未来趋势明显

市场火热,资本加持 昨日,深圳核心医疗科技股份有限公司自主研发的Corheart 6植入式左心室辅助系统获得国家药品监督管理局批准上市。这是一枚完全国产,拥有完备自主知识产权的人工心脏。该产品正式获批上市,加之之前获批的永仁心…

C++ 参数的三种传递方式和应用参加

C 参数的三种传递方式分别是值传递、指针传递和引用传递。 值传递 值传递的实质 将实参的值(a、b)复制到形参(m、n)相应的存储单元中,即形参和实参分别占用不同的存储单元。 值传递的特点 值传递的特点是单向传递,即主调函数…

「C/C++」C/C++ 回调函数

✨博客主页:何曾参静谧的博客 📌文章专栏:「C/C」C/C程序设计 相关术语 回调函数:是一种常用的编程技术,它可以将一个函数作为参数传递给另一个函数,并在后者执行过程中调用前者。回调函数通常用来处理异步…

python怎么搭建免费代理IP池,免费代理IP适合爬虫工作吗

Python可以使用一些第三方库和工具来搭建免费代理IP池。简单来说,搭建代理IP池的步骤如下: 1. 获取代理IP:从一些免费或付费代理IP网站上爬取并验证IP地址和端口信息。 2. 验证代理IP:使用代理IP访问一些网站或服务,验…

什么是IT服务请求管理

什么是服务请求 用户每天都会提出各种 IT 请求。它可能是对新软件的请求、旧硬件的更换、对应用程序的访问或资产组件的更改。这些请求被归类为服务请求。 服务请求是向 IT 团队发出的请求,以满足最终用户的需求。理想情况下,请求是从服务请求目录中选择…

优维低代码实践:编排优化Plus

优维低代码技术专栏,是一个全新的、技术为主的专栏,由优维技术委员会成员执笔,基于优维7年低代码技术研发及运维成果,主要介绍低代码相关的技术原理及架构逻辑,目的是给广大运维人提供一个技术交流与学习的平台。 优维…

管理项目-加载菜单

人事管理项目-加载菜单 引入ElementUI菜单1.菜单模板 引入ElementUI 数据成功访问后,引入ElementUI组件对数据进行渲染,首先引入其依赖: npm install element-ui -S 依赖添加成功后,接着在main.js中引入ElementUI&a…

G2上看看Notes/Domino

大家好,才是真的好。 Engageug2023正在如火如荼进行,今年的主题是“The Future is Now”。 开场就发布了很多Notes/Domino以及相关产品的全新路线图,例如今年第四季度发布的新的Domino 14会直接集成Verse邮箱支持、Nomad Web功能&#xff0…

MySQL学习-数据库创建-数据库增删改查语句-事务-索引

MySQL学习 前言 SQL是结构化查询语言的缩写,用于管理关系数据库(RDBMS)中的数据。SQL语言由IBM公司的Donald Chamberlin和Raymond Boyce于20世纪70年代开发而来,是关系型数据库最常用的管理语言。 使用SQL语言可以实现关系型数据库中的数据处理、数据…

MySQL模糊查询再也不用like+%了

前言 倒排索引 全文检索 创建全文索引 使用全文索引 删除全文索引 小结 前言 我们都知道 InnoDB 在模糊查询数据时使用 "%xx" 会导致索引失效,但有时需求就是如此,类似这样的需求还有很多,例如,搜索引擎需要根基…

记录一个Invalid bound statement (not found)问题

SpringBootMyBatisPlus项目&#xff0c;非常简单&#xff0c;没有任何业务逻辑&#xff1a; 1. pom文件 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.…

Windows定时执行Python脚本

在Linux环境下我们可以使用crontab工具来定时的执行脚本&#xff0c;可以很轻松的管理各个虚拟环境下的py文件在Windows上可以使用任务计划程序来定时执行我们的脚本 关于这个的基本使用可以查看我前面的博客 https://blog.csdn.net/wyh1618/article/details/125725967?spm10…

单片机编程小错记录1:漏写串口中断服务函数

问题描述&#xff1a; 原本单独测试没问题的定时器程序&#xff0c;但在加上串口程序后出现了问题&#xff0c;发现定时器貌似没有在正常工作...... 问题发现&#xff1a; 程序如下&#xff1a; 该程序主要想实现的功能是&#xff1a; 1.串口在初始化时发送"welcome\r…