学习递归的另一种方法

article/2025/10/16 8:08:19

每个学期,我都会通过一项调查,以获取有关我的教学的一些反馈。 上学期终于有人给我写一篇新文章的想法。 特别是,他们想了解有关递归的更多信息,所以我认为我会综合一些技巧。

递归概述

对于那些可能是第一次学习递归的人,我想我会提供一些有关概念的概述。

特别地,递归是一种依赖于解决较小子问题的问题解决技术。 换句话说,不是继续直接解决问题,而是继续分解问题,直到找到可以实际解决的较小问题为止。 然后,我们使用最小子问题的答案来进行倒推,直到获得原始问题的答案为止。

例如,假设我们要计算2 6 。 通常,我们会将其分解为重复乘法。 换句话说,我们将2乘以6乘以自身,得出64。在计算机科学中,我们将此称为问题解决技术迭代,通常以循环的形式看到它:

 def iterative_power(base, exponent): product = 1 for i in range(exponent): product *= base return product  print(iterative_power( 2 , 6 )) 

当然,如果我们已经知道一些较小的子问题的答案,该怎么办。 例如,如果我们知道2 5是32怎么办? 然后,我们可以通过将2 5乘以2来直接计算2 6。这就是递归的思想:

 def recursive_power(base, exponent): if exponent == 0 : return 1 else : return recursive_power(base, exponent - 1 ) * base 

在本文中,我们将介绍递归的另一种思考方式。 希望它可以帮助您比以前更好地理解概念。

为什么递归如此困难?

虽然函数式编程目前正在卷土重来,但是它对我们学习编码的方式并没有产生太大的影响。 结果,大多数人开始使用类似C的命令式编程语言进行编码。 例如,我开始使用Java并转入C,C ++,C#和Python等语言。

不幸的是,学习像C这样的语言的弊端在于,我们在解决问题的思维方式上受到了一定的限制。 换句话说,以这些语言提供给我们的工具集在很大程度上偏向分支(即,如果语句和循环)。

结果,递归背后的直觉并不是从一开始就建立的。 取而代之的是,我们被迫使用与迭代解决方案相关的人为示例引入递归。 否则,我们还会如何联系。

另外,像C这样的命令性语言在引入递归之前需要大量的开销。 例如,在开始考虑递归之前,您可能必须学习变量,分支和函数等概念。 实际上,直到我第一次学习数据结构课程时,我才亲自介绍这个概念。

总而言之,可能很难掌握递归,因为它需要您对编码的理解成为现实。 幸运的是,有多种方法可以打破这种循环( 绝对是双关语)。

通过合同设计学习递归

最近,我在俄亥俄州立大学接受培训以教授软件组件课程,而该课程涵盖的主题之一是递归。 当然,那时我已经非常熟悉递归,但是我认为他们教这个主题的方式确实很优雅。 结果,我认为我可以将该方法传递给您。 也就是说,我们需要首先解决按合同设计。

合同设计概论

如果我在过去写过一些有关“按合同设计”的文章( 主要是指JUnit测试) ,请再说一遍,对此我深表歉意。 自然地,我认为我现在是一个稍微好一点的作家,但也可以随时参考该文章。

无论如何, 按合同设计 (DbC)是一种编程哲学,在该哲学中,我们根据合同来考虑功能。 换句话说,当用户使用我们的功能之一时,我们希望为他们提供某种保证。 特别是,我们要求用户在一定条件下(又称为前提条件)执行每个功能。 因此,我们保证每个函数都会以某种特定的方式运行(aka后置条件)。

DbC在递归中很重要,因为它允许我们精确地指定函数在特定条件下将执行的操作。 例如,只要我们指定一些先决条件,我们先前定义的幂函数将起作用:

  • 两个参数都必须是整数(可以通过一些类型提示来解决)
  • power参数不能为负

其结果是,我们的承诺,这两个函数将返回base提出了一些power 。 如果用户输入了无效的输入,我们将不会做出任何承诺,因为他们显然违反了合同。 当然,我们总是可以提供某种输入验证来防止令人讨厌的错误,但这超出了DbC的范围。

所有人免费午餐

在OSU中,我们通过完全忽略递归的思想来介绍递归。 取而代之的是,我们利用对合同设计的了解,开始隐式地实现递归函数。

例如,让我们再看看我们一直在谈论的这个幂函数:

 def power(base: int , exponent: int ) -> int : "" "Computes the base ^ exponent. Precondition: exponent >= 0 Postcondition: base ^ exponent "" " return FreeLunch.power(base, exponent) 

在此示例中,我们从名为FreeLunch的神奇库中引入了新的幂函数。 就我们而言,这一新的幂函数与我们编写的函数完全相同,即相同的合同。

现在,为了论证,让我们说这个FreeLunch功效函数有一个要求:其输入必须比功效函数的输入“小”。 我们怎么做才能确保这项工作有效?

好吧,如果我们减小指数,我们可以通过乘以底数(即x 5 = x 4 * x)将其加回去。 让我们尝试一下:

 def power(base: int , exponent: int ) -> int : "" "Computes the base ^ exponent. Precondition: exponent >= 0 Postcondition: base ^ exponent "" " return FreeLunch.power(base, exponent - 1 ) * base 

在这一点上,我们如何去验证它是否有效? 好吧,让我们尝试一些输入。 例如,我们可以再次尝试2 6 。 如果我们遍历代码,就会注意到我们调用了FreeLunch.power(2, 5) ,这是一个完全有效的输入。 换句话说,我们将得到32,然后乘以2得到64,这是正确的答案。

也就是说,是否有任何输入会失败? 绝对! 由于FreeLunch功效函数与我们的FreeLunch函数具有相同的合同,因此存在无效的输入。 例如,我们应该避免FreeLunch幂函数的值小于0的任何情况:

 def power(base: int , exponent: int ) -> int : "" "Computes the base ^ exponent. Precondition: exponent >= 0 Postcondition: base ^ exponent "" " if exponent == 0 : return 1 else : return FreeLunch.power(base, exponent - 1 ) * base 

现在,可以确定我们的幂函数可以工作了。

天下没有免费的午餐

您可能会想到,以这种复杂的方式实现幂函数只是一种欺骗您使用递归的练习。 换句话说,我们可以完全删除上面的FreeLunch类,并且我们将拥有一个功能齐全的递归解决方案:

 def power(base: int , exponent: int ) -> int : "" "Computes the base ^ exponent. Precondition: exponent >= 0 Postcondition: base ^ exponent "" " if exponent == 0 : return 1 else : return power(base, exponent - 1 ) * base 

现在的问题是:为什么我们要经历所有这些努力来引入递归? 毕竟,几乎所有其他递归教程都利用某种堆栈。 我们为什么不这样做呢?

虽然使用堆栈教递归完全有效,但对学生来说通常很麻烦。 例如,想象一下在不使用我们的FreeLunch技巧的情况下通过上面的幂函数进行跟踪。 如果我们提供两个随机输入(例如2的底数和6的指数),则必须跟踪每个幂函数调用,直到达到最小的子问题为止。 在纸面上,这是一个简单示例的六种痕迹!

通过这种方式进行授课时,学生在尝试各种程序输入时经常会发现自己迷失在递归中。 如果他们转而放弃对合同的怀疑和信任,他们可以一次证明自己的功能有效。 当然,他们需要特别注意其输入( 请参阅数学归纳法 )。

递归根

现在,我们已经看到了一些示例,我想谈一谈递归的来源。 毕竟,递归通常是这个令人恐惧的主题,在计算机科学讲座中,方便时似乎就会出现。 幸运的是,它拥有比这更有趣的历史。

还记得我们之前看过的幂函数吗? 那不是偶然。 实际上,存在大量可以递归编写的数学公式。 例如,幂公式如下所示:

a n = a n-1 * a(如果n> 0)

自然地, n可以连续分解,直到指数为1或0为止,这取决于我们希望终止递归的方式。 在上面的示例中,当n = 1时我们忽略了,因为n = 0给我们1很好地工作了。 换句话说,为n = 1添加一个额外的情况对结果没有影响。

像幂一样,您可能已经知道其他几个递归公式。 例如,斐波那契数列由递归公式定义:

a n = a n-1 + a n-2

为确保此公式有效,我们必须定义前两个术语。 然后,一切正常。 根据您询问的人,前两个术语可以是0和1或1和1。无论如何,这两对数字都可以工作。

如果要随后计算序列中的任意项,则可以分解原始函数,直到遇到任何一种基本情况。 例如,下图说明了如何计算斐波那契数列中的第五项:

我们可以整天争论这种递归算法的效率,但这是解决问题的有效方法。 而不是逐步解决问题,我们将问题分解为较小的问题,直到找到可以解决的问题为止。 然后,我们使用该结果向后工作,直到获得最终答案。

递归结构

既然我们已经从数学和契约设计的角度研究了递归,那么问题就变成了:我们如何认识到递归有益的问题? 换句话说,是否存在某种形式的递归结构问题? 让我们来谈谈它。

事实证明,我们周围都有递归结构。 例如,斐波那契数列被明确定义为递归公式。 当然,还有其他类型的递归结构。 例如,上面的树图是一个递归结构。 毕竟,每次对“ fib”的调用都会创建另一棵树。 换句话说,树内有树木。

在技​​术之外,还有诸如分形之类的递归结构的其他示例,这些分形是几何形状的集合,可在任何规模上保持其结构。 对于熟悉Triforce的人来说 ,这是一个简单的分形的完美示例:一个由三角形组成的三角形。

同样,每天都有几种递归结构,例如目录(包含文件夹的文件夹)和洋葱(包含洋葱的洋葱)。 换句话说,层次结构和嵌套结构通常是递归的良好指示。

从这些示例中,我们可以识别出任何类型的模式吗? 嗯,当然! 这里的关键是观察问题的结构。 问题本身是否由类似的问题组成? 如果是这样,则递归可能是可行的方法。 如果没有,尝试其他方法可能更有意义。

著名的递归算法

综上所述,我认为将一小段著名的递归算法用于启发是很有帮助的。 随时在评论中分享您的一些收藏夹:

  • 合并排序和快速排序
  • 阶乘 , 幂和斐波那契
  • 最大公约数
  • 河内塔
  • 树遍历
  • 深度优先搜索

至此,我认为我们涵盖了所有内容。 如果您有本文未涉及的任何问题,请随时将其引向下面的评论。

参见递归

如果您想了解有关递归的更多信息,我建议您阅读这篇很棒的文章,名为“ 另一种学习递归的方法” (对不起,我至少要开一个递归的笑话)。 否则,谢谢您的光临!

如果您想坚持下去,我还有很多其他文章适合您:

  • 语句和表达式之间的区别
  • 使用模块化算术的剪刀石头布
  • 注意Java中String的Substring方法

再次感谢您的支持。 每一点都走很长的路!

翻译自: https://www.javacodegeeks.com/2019/08/yet-another-way-learn-recursion.html


http://chatgpt.dhexx.cn/article/Z5CvjXWJ.shtml

相关文章

用非递归方法实现递归算法时_学习递归的另一种方法

用非递归方法实现递归算法时 每个学期,我都会通过一项调查,以获取有关我的教学的一些反馈。 上学期终于有人给我写一篇新文章的想法。 特别是,他们想了解有关递归的更多信息,所以我认为我会综合一些技巧。 递归概述 对于那些可能…

bat 文件夹移动

echo off echo hello world ::得到当天 set pathLog%date:~0,4%%date:~5,2%%date:~8,2% :: eg:能得到 秒:set pathLog%date:~0,4%%date:~5,2%%date:~8,2%%time:~0,2%%time:~3,2%%time:~6,2% :: set pathLog%pathLog: 0% ::创建文件夹 md "d:\log\log%pathLog% ec…

【Python零基础入门篇 · 19】:os模块、可迭代对象和迭代器对象

目录 一、os模块 1、os模块中的命令: 2、常用命令的代码演示 os.getcwd() os.chdir(path) os.listdir(path) os.mkdir(path) os.makedirs(path) os.rename(旧名,新名) 3、举例:查找文件夹下所有满足要求的文件 二、可迭代对象和迭代器对象 1、…

day18:File(构造方法、创建、删除文件或者文件夹、 判断性、重命名与剪切、得到性、过滤性)、递归(遍历文件夹文件)

一 回顾 1.HashMap集合 特点: A.数据结构也是Hash表结构 B.多线程中是不安全 C.默认的数组的初始化容量是16 2.HashMap与HashSet的比较 相同点:都是hash表结构来存储 不同点: A.HashSet的底层就是使用HashMap来实现 B.HashSet的数据结构针对与是元素 HashMap的…

Python 文件和文件夹 01

Python文件和文件夹 01 ① 修改当前目录,首次利用 pandas 读取 excel 表 os.chdir import os import pandas as pd os.chdir(C:/aa/bb/cc) os.chdir(rC:\aa\bb\cc)数据 pd.read_excel("temp.xlsx") 等同于当前路径下的 temp.xlsx 文件。print(数据)② 字…

删除win10无限嵌套文件夹

解决由于失误操作导致WIN10系统产生无限循环的文件夹问题 昨天本想自己写一个拷贝文件的小程序,结果出现了点小问题,整出了一个无限循环的文件夹,直接删除出出现错误代码提示,显示无法删除,然后我就去网上找解决方案&…

计算机专硕一般研二在干嘛,专硕一般研二在干嘛,专硕两年怎么安排

一般学习两年。 硕士学位的学制取决于您申请的学校和专业。 不同的学校可能不同,同一所学校的硕士学位也可能不同。 许多学校还设有两年半的学制,甚至三年制的学制。本文一起来看一下吧~ 一.什么人适合读专硕 1、英语相对一般的人 学硕主要考英语一试卷&…

研二导师画大饼,不给时间实习,咋办

一个小学弟最近咨询我 我是本硕都在一所双非一本就读,软件工程,目前研二,23 届。我想在暑期进行一下今年的实习,想着可能对后面秋招和来年春招有帮助,但是实验室管的严导师不放时间(其实我当时是为了毕业条件和平时研…

2022年终总结与2023新年展望

前言 时间过得太快了,虽然写博客已经很多年了,但是年终总结一直由于种种原因没有写过,2022年确实是魔幻的一年,不知不觉自己也已经研二了,因为疫情的原因突然放开,提前放假回家,借此机会写一下…

研二(上学期)计划安排

今天是9.17号了,时间过得很快,学习的脚步永远停不下来。 时间的安排就不说了,真的是计划赶不上变化,一句话,除了外聘上课和研助,其他的时间必须到达实验室,(一个星期一次总结&#…

计算机科学与探索、计算机工程与应用投稿经验分享

目录 等级: 经验: 总结: 等级: 计算机科学与探索 CCFb 计算机工程与应用 CCFB(2022年由c升为b) 经验: 首先本人主要关注计算机人工智能图像处理领域,北京某高校研二学生,水平不高。在研一…

计算机专业学生如何写一份优秀的校招简历(大三、研二学生请进)

计算机专业学生如何写一份优秀的校招简历(大三、研二学生请进) 最近讲了一节简历的公开课,还是蛮有价值的,想分享给大家 主要是讲解计算机相关专业的学生,就业找工作的简历,该如何书写。 内容包含: 1、快速掌握一份校…

快速傅里叶变换(研二的我终于弄懂了)

研二的我仍然对快速傅里叶变换一知半解,于是乎,本着待在家里,能耗时间就多耗点,不知道何年马月我才可以在外面快乐的奔跑~~ 快速傅里叶变换的实现(c版本) 在做项目的时候,需要用到matlab里的ff…

华电软工非全研究生学习工作总结-研二开学总结

昨晚加班太晚,就打算调休一天,养好精神,晚上开车回老家,开启假期模式。午休过后没啥事,随后就想着水一篇文章吧。 1、研究生学习 1.1、学生证来啦 今天对学生们来说,最大的喜事就是,期待了一学期的研究生学生证从北京邮寄了。看到微信群同学们开心的晒着学生证,我也期…

爆肝三天,我整理了这份春招攻略【针对大三/研二】

大家好,我是菜饼。长文预警,建议收藏。 18级的师弟妹们,这份春招攻略,希望可以让你们清醒一下。 (当然,本篇不仅仅适用于大三同学,也适用于研一研二,打算走互联网开发方向的同学。&…

再见北理工:忆北京研究生的编程时光

两年前,我本科毕业写了这样一篇文章:《 回忆自己的大学四年得与失 》,感慨了自己在北理软院四年的所得所失;两年后,我离开了帝都,回到了贵州家乡,准备开启一段新的教师生涯,在此也写…

研究生学姐二次考研的感悟:关于择校选专业专硕or学硕

今天想跟大家分享一下我第一次考研,第二次考研,以及现在读研的一些经历。如果你能从中获得启发,我很荣幸,如果你觉得我说得不对,那就是你对。以下我输出的观点仅代表我个人,每个人的成长环境和想法都不一样…

优秀!研二实习生“阿里+字节+拼多多+美团”四杀offer

本人就读于某无导师制培训班,研二在百度腾讯实习过,目前想转java技术栈或wlb一下,就投递了一些外企和美团阿里,至于字节与拼多多,个人实在无法接受周末上班,就没有投递了。 年前准备了一下简历&#xff0c…

研一一整年都在搞深度学习,研二醒悟打算转开发

作者:阿秀阿秀的学习笔记:https://interviewguide.cn 你好,我是阿秀。 最近阿秀组建了自己的学习圈子,其实圈子里以前只有我一个人的,每天适当充电、看看书或者看一些教学视频,也会简单打卡记录自己的学习进…

【阶段总结】研二上学期总结

写在前面 距离上一篇【阶段总结】研一上学期总结又过去了将近一年的时间,而这一篇的阶段性总结也是在我入驻csdn平台后的第四篇的年度总结。从一开始的犹犹豫豫到现在坚持不定期的写作和总结,回想这几年的历程,还好有个csdn这个平台可以记录…