探讨递归这个引人入胜的领域

2023-11-05

又回来念一念递归的经,编程之旅上有笔直的大路,也有盘一盘的递归? 今天探讨递归是引人入胜的思想 — 递归也许是上帝做工的方式。函数调用自己则体现了内在统一的秩序,

递归究竟是什么?

每年我们都会长一岁,试试定义一个函数 deltaYear只做一件事情,就是在输入的age上的年龄上 +1 (增加 1)

def deltaYear(age):
    if age == 996:return
    #age = 997 就报错!
    
    print('age',age)
    return deltaYear(age+1)
print(deltaYear(age=0))

deltaYear(age)一直不断地调用自己,age变量也随之而变化!

这也是大喵能想到的最简洁的递归栗子,直观地看到 age = 997 时,程序出现错误:

RecursionError: maximum recursion depth 
exceeded while calling a Python object

请参见最后几步的输出:

 ...
 
age 990
age 991
age 992
age 993
age 994
age 995
Traceback (most recent call last): 
  [Previous line repeated 993 more times]
  File "C:\Users\29358\PycharmProjects\Python_workspace-main\4 ddm_course\02_Python inner function\RecursionABC.py", line 7, in deltaYear
    print('age',age)
RecursionError: maximum recursion depth exceeded while calling a Python object

爆栈的次数发生在什么时候?

好了,让我们戴上探险家的帽子。想象一下,你身处一个神奇的森林。是的,编程可以是神奇的!

你偶然发现了一棵会说话的树,它要求你数一下它的树枝上的所有叶子。

但有一个小插曲 — 这棵树的树枝分成了更小的树枝,而那些树枝又分成更小的树枝。简而言之,你需要数在嵌套的叶子中的叶子。

编程中的递归有点像这个情景 — 一个函数调用自身来解决问题,问题会被分成越来越小的部分。

递归函数的要点

那么,你听说过“递归函数”这个术语,它听起来有点神秘,对吧?好吧,让我们来揭开它的神秘面纱。

递归函数就像一个解谜伙伴。它将一个复杂的问题分解为相同问题的简化版本,直到达到基本情况 — 题目中最小的可解部分。

让我们来看一个经典的例子 — 计算阶乘。假设你想找出一个数 n 的阶乘。

你知道 n! 就等于 n 乘以 (n-1)!,而 (n-1)! 又等于 (n-1) 乘以 (n-2)!,以此类推,直到我们得到 1!,它就是 1。

那么,这在 Python 中是这样的:

def factorial(n):
    if n == 1:
        return 1
    else:
        return n * factorial(n - 1)

看到了吗?我们使用相同的函数将问题分解成更小的部分,最终达到递归停止的基本情况。

信任之舞:基本情况和递归情况

好了,想象一下,你正在举办一个派对,邀请了一些朋友。你告诉你的朋友邀请他们的朋友,而他们的朋友又邀请更多的朋友。

但是,有个问题 — 你至少需要一个朋友来开始这个连锁。在递归中,这个“一个朋友”就是你的基本情况。如果没有它,你就会陷入无限循环的派对策划中!

在我们的阶乘例子中,基本情况是当 n 等于 1 时。这就是开始派对的朋友。剩下的链条,我们在其中将 n 与 (n-1)! 相乘,就是递归情况。就像每个朋友都邀请他们自己的一组朋友一样。

解开魔法:调用栈

好的,让我们看一看幕后情况。当你在 Python 中调用一个函数时,计算机会创建一个调用栈 — 这是一个函数调用的堆栈的花哨说法。

在我们的递归旅程中,每次调用阶乘函数时,一个新的函数实例都会被添加到堆栈中。但请记住,堆栈的空间是有限的!

所以,当我们深入递归时,堆栈会变得更高。但当我们达到那个神奇的基本情况时,堆栈开始缩小,因为每个函数调用都会完成并返回其值。这就像解开礼物一样 — 你一层一层地剥开,直到达到里面的礼物!

小心:无限陷阱

现在,我的朋友们,在触发递归末日之前,请记住那个至关重要的基本情况。如果你忘了它,你将发现自己陷入无限的函数调用坑中 — 那可不是一个有趣的派对!

总结递归的乐趣

所以,这就是 Python 中递归的世界,一点也不可怕。我们已经解决了基础知识 — 理解递归的概念,区分基本情况和递归情况,以及瞥见调用栈的神奇之处。

递归就像一个解谜巫师,将问题分解成一口口大小的块。只要有正确的基本情况和对如何组合这些块的清晰理解,你将能够创建递归函数,令即使是最智慧的编程树也会赞叹不已。

汉诺塔

MIT的CS课堂对递归的理解并没有用上述的栗子。递归不仅是函数调用自己本身,还有什么精妙之处被以上的栗子忽略?

经典的做法是用汉诺塔理解递归。汉诺塔之所以经典,是因为不同于前面谈到的两个栗子,只包含递归的一个要素:函数自己调用自己。

alt

图示有 8 个Disk,A, B, C三个柱。任务是将A柱的disk全部移动到C 柱即移动到最右侧的柱,还原为A 柱的叠放状态:保持较小的总是在较大的上面。

搬动过程中要满足两个条件,搬动过程中:每次只能搬动一片;每一次搬动保证尺寸大的Disk不能放在小的Disk上面。

试一试看?

自上而下将 8 个disk分别叫1、2 .... 7、8号盘,任务目标是搬动 8 个盘到 C 柱。

那么,任务等价于最先将 8 号盘放在 C的最下面,再将1-7号盘放在 8 之上。

注意我的表述方式,任务分为两个子任务。

def tower_of_hanoi(n, source, auxiliary, target):
    if n == 1:
        print(f"Move disk 1 from {source} to {target}")
        return
    tower_of_hanoi(n-1, source, target, auxiliary)
    print(f"Move disk {n} from {source} to {target}")
    tower_of_hanoi(n-1, auxiliary, source, target)

# 示例用法
tower_of_hanoi(4, 'A''B''C')

输出结果:

Move disk 1 from A to B
Move disk 2 from A to C
Move disk 1 from B to C
Move disk 3 from A to B
Move disk 1 from C to A
Move disk 2 from C to B
Move disk 1 from A to B
Move disk 4 from A to C
Move disk 1 from B to C
Move disk 2 from B to A
Move disk 1 from C to A
Move disk 3 from B to C
Move disk 1 from A to B
Move disk 2 from A to C
Move disk 1 from B to C

C++:

cpp

#include <iostream>

void tower_of_hanoi(int n, char source, char auxiliary, char target) {
    if (n == 1) {
        std::cout << "Move disk 1 from " << source << " to " << target << std::endl;
        return;
    }
    tower_of_hanoi(n-1, source, target, auxiliary);
    std::cout << "Move disk " << n << " from " << source << " to " << target << std::endl;
    tower_of_hanoi(n-1, auxiliary, source, target);
}

int main() {
    int n = 3; // 汉诺塔的盘子数
    tower_of_hanoi(n, 'A''B''C');
    return 0;
}

这两个示例代码都实现了汉诺塔问题的解决方案。在这个问题中,有三个杆(通常称为A、B和C),以及一些不同大小的圆盘,开始时都放在一个杆上。

目标是将所有圆盘从起始杆移动到目标杆,遵守以下规则:

一次只能移动一个盘子。

任何时候,大盘子不能放在小盘子的上面。

这两个示例都使用递归来解决问题,因为汉诺塔问题本身就是递归性质的经典问题。这些代码将打印出移动每个盘子的步骤,以完成整个汉诺塔谜题。

当你解决汉诺塔问题时,搬动的总次数可以通过数学公式来计算,而不需要实际模拟每一步的搬动。下面是一个计算搬动次数的Python程序:

def hanoi_moves(n):
    if n == 1:
        return 1
    else:
        return 2 * hanoi_moves(n - 1) + 1

#示例用法

num_disks = 3  # 汉诺塔的盘子数
total_moves = hanoi_moves(num_disks)
print(f"移动的总次数:{num_disks} disks: {total_moves}")

这个程序使用递归方式计算汉诺塔问题所需的总搬动次数。

公式为:Total moves = 2**n - 1

alt

其中,n 是汉诺塔的盘子数。

在示例用法中,我们计算了有3个盘子的汉诺塔问题所需的总搬动次数。你可以根据需要更改 num_disks 的值来计算其他盘子数量的搬动次数。

本文由 mdnice 多平台发布

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

探讨递归这个引人入胜的领域 的相关文章

  • linux读写锁pthread_rwlock_t

    读写锁说明 读写锁实际是一种特殊的自旋锁 它把对共享资源的访问者划分成读者和写者 这种锁相对于自旋锁而言 能提高并发性 它允许同时有多个读者来访问共享资源 写者是排他性的 一个读写锁同时只能有一个写者或多个读者 但不能同时既有读者又有写者
  • 巴比特

    摘要 据钛媒体报道 当前市场对与内容相关的产品商业化的耐心不及以往 以至于 扎克伯格早早就为元宇宙设想好了商业化的落地场景 元宇宙方面 我们希望能够为用户提供更多业务帮助 比如通过聊天直接获取客户 直接对用户销售产品 提供客服帮助或再向用户
  • 国内NFT平台及玩法一览

    2021年被称为NFT的 元年 互联网巨头 各大企业 艺术家 明星纷纷入局NFT 屡创新高的NFT价格更是让其成为大众关注焦点 此推出NFT系列专题研究 盘点和总结NFT的发行市场 平台背景 投融资状况 市场热点 政策监管等相关内容 帮助读

随机推荐

  • c语言static关键字的理解

    static 一 概述 在c语言中static恰当的使用能让程序更加完美 细节上的严谨 代码会更好 也更利于程序的维护与扩展 而static使用灵活 且又有两种完全无关的用法 所以整理总结一下 二 static的两种用法 1 static修
  • Tomcat项目配置

    1 下载tomcat 版本 9 0 65 链接 https tomcat apache org download 90 cgi 2 解压tomcat tomcat直接解压就可以使用 解压后文件夹目录为 3 bin文件夹下 startup b
  • vue.js -- 全局组件&局部组件

    目录 vue组件 全局组件 组件定义 组件复用 组件的组件 局部组件 总结 vue组件 组件是 Vue js 最强大的功能之一 组件可以扩展 HTML 元素 封装可重用的代码 组件系统让我们可以用独立可复用的小组件来构建大型应用 几乎任意类
  • JAVA逻辑思维题(1)4个人过桥

    有4个女人要过一座桥 她们都站在桥的某一边 要让她们在17分钟内全部通过这座桥 这时是晚上 她们只有一个手电筒 最多只能让两个人同时过桥 不管是谁过桥 不管是一个人还是两个人 必须要带着手电筒 手电筒必须要传来传去 不能扔过去 每个女人过桥
  • vue ag-grid-vue 大数据动态加载服务器请求数据

    vue ag grid vue 渲染大数据表格 动态请求服务器数据设置子目录 目前只能是初始是一次性加载所有数据再分页渲染 还没有研究出滚动分页加载服务器 这个方法是一次加载所有数据在前端再做的分页渲染 点击目录文件展开能动态加载服务器的数
  • 计算机上应用锁,电脑怎么设置软件锁

    题主 针对这个问题有两个解决方案 一 设立两个账户 先创建一个用户 右键计算机 点管理 在弹出的对话框选择用户和组 选择用户 右键空白地方新建用户 设置密码等选项 新建完成关闭对话框 找到软件快捷方式或者软件图标 右键图标 选择属性 在跳出
  • 个人服务器环境搭建

    前言 文档创建时间 2022年12月1日14点26分 初衷 想搭建个人服务器 熟悉Linux的语言以及服务部署相关事情 主要方向可能是个人网站 公众号 小程序后台接口 之前有在免费的服务器上面搭建过 现在再次搭建顺便做个记录 前期准备 云服
  • 学习笔记(一):Windows和Ubuntu系统下的QGIS-python二次开发环境配置方法

    学习笔记 一 Windows和Ubuntu系统下的QGIS python二次开发环境配置方法 过程超级超级无敌详细 0 写在前面 1 Win10系统下QGIS python的开发环境配置 1 1 方法一 使用QGIS软件中的bat文件直接配
  • [Java基础系列第1弹]一文带你了解Java编程语言的精髓:特性、机制、环境和工具

    如果你是一个想要学习编程的新手 或者是一个想要提高编程技能的老手 那么Java编程语言是一个非常好的选择 Java是一种简单 面向对象 分布式 健壮 安全 跨平台 多线程和动态的编程语言 它可以用于开发各种类型和规模的软件应用 Java是目
  • 大厂经典Zookeeper面试题整理汇总

    1 ZooKeeper 是什么 ZooKeeper 是一个分布式的 开放源码的分布式应用程序协调服务 它是一个为分布式应用提供一致性服务的软件 提供的功能包括 配置维护 域名服务 分布式同步 组服务等 ZooKeeper 的目标就是封装好复
  • 在传统公司干IT是一种什么体验(三)

    我的同事人人都是福尔摩斯 我的一举一动都是他们的破案素材 表哥语录 表哥的公司特别重视细节 注重细节本来是一个良好的工作习惯 但是表哥公司的同事把细节用到了极致 可能也是一种内卷的表现 公司里的人基本都干了10年了 公司业务变化也不大 天天
  • 通过url下载文件和本地路径下载文件是不一样的!

    通过url下载文件 RequestMapping value download method RequestMethod GET public void getRequest HttpServletResponse response Str
  • 【微信小程序】从后台获取数据并赋值到数组,然后前端遍历使用。

    一 在 js文件的data里面创建一个空数组 用来存放后端返回的数据 Page data ToDoList 二 后端文件
  • WSL2 使用 docker

    一 下载docker 这篇文章发布的时候正式版的docker 仍然不支持docker wsl 因此需要下载edge 版本 下载地址 https docs docker com docker for windows edge release
  • 献给Python初学者,零基础学习Python能学会吗?

    零基础 学习 Python 能学会吗 这个问题几乎是所有初学Python的小白都会问到的问题 其实Python是非常适合初学者入门的 相较于其他主流类编程语言 Python具有更好的可读性 因此上手更容易 而且即便你是零基础也一样能学会 零
  • 【深度学习】(二)神经网络:激活函数、MNIST

    感知机需要人为设定符合预期输入输出的权重 神经网络可以自动地从数据中心学习到合适的参数 质朴感知机 指单层网络 激活函数使用了阶跃函数的模型 多层感知机 指神经网络 使用平滑的激活函数的多层网络 激活函数 激活函数是将输入信号的总和转换为输
  • sql语句中not in 不好使的原因之一

    场景说明 查询某表中的某字段的值没有在另外一个表中对应的字段中出现过 比如现在有两个表 一个产品表product 一个优惠券批次表coupon coupon中的product code字段与product中的product code形成一对
  • 基于selenium实现UI自动化

    目录 一 Selenium简介 1 Selenium工具组件介绍 2 Selenium WebDriver 介绍及实现原理 二 Selenium WebDriver基于Python实现脚本 1 Selenium 环境安装 2 Seleniu
  • 将Vscode添加右键打开文件夹功能

    将Vscode添加右键打开文件夹功能 文章目录 将Vscode添加右键打开文件夹功能 前言 1 将Vscode添加右键打开文件夹功能 1 1 第一种方法 1 2 第二种方法 总结 前言 想要鼠标右击文件或者文件夹 可直接用VSCode打开
  • 探讨递归这个引人入胜的领域

    又回来念一念递归的经 编程之旅上有笔直的大路 也有盘一盘的递归 今天探讨递归是引人入胜的思想 递归也许是上帝做工的方式 函数调用自己则体现了内在统一的秩序 递归究竟是什么 每年我们都会长一岁 试试定义一个函数 deltaYear只做一件事情