递归函数python有什么特点_Python中的递归

在前面的讲解中,函数的调用通常发生在彼此不同的函数之间。其实,函数还有一种特殊的调用方式,那就是自己调用自己,这种方式称为函数递归调用。

递归,在程序设计中也是一个常用的技巧,甚至是一种思维方式,非常值得我们掌握。

4.3.1 感性认识递推

在讲解“递归”这个抽象概念之前,让我们来重温一下昔日往事。小时候,当我们在缠着长辈讲故事时,长辈们可能就用下面的故事来“忽悠”我们:从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事!故事是什么呢?从前有座山,山里有座庙,庙里有个老和尚正在给小和尚讲故事!故事是什么呢……

除非讲故事的人自己停下来不讲了,不然这个故事可以“无限”讲下去,原因就是“故事”嵌套的“故事”就是“故事”本身,这就是语言上“递归”的例子。

但是,由于这个故事并没有一个终止的条件,因此,它实际上是陷入了一种有头无尾的死循环,因此并不符合程序设计领域中定义的“递归”。在程序设计领域,递归是指函数(或方法)直接或间接调用自身的一种操作,如图4-4所示。递归调用的好处在于,它能够大大减少代码量,将原本复杂的问题简化成一个简单的基础操作来完成。在编写程序的过程中,“递归调用”是一个非常实用的技巧。

图4-4 递归示意图

从图4-4中可以看出,函数不论是直接调用自身,还是间接调用自身,都是一种无终止的过程。

在程序设计中,显然不能出现这种无终止的调用。因此,在编写递归算法时,读者要特别注意,所有递归一定要有终止条件,这又被称作递归出口。

如果一个递归函数缺少递归出口,执行时就会陷入死循环。递归出口通常可用if语句来设置,在满足某种条件时不再继续,调用某个值,结束递归。

谷歌公司有世界上最聪明的程序员。他们不光聪明,还很有自己的“冷幽默”,别出心裁。比如说,假设你不懂得什么是“递归”,不妨去谷歌搜索一下这个关键词。

然后你会发现,除了给出必要的搜索结果,谷歌还给出了一个提示语“您是不是要找:递归”,如图4-5所示。

图4-5 谷歌程序员的“冷幽默”

咋一看,你可能会觉得,这谷歌搜索是不是有问题啊?我的确、明明、丝毫无误地查询的就是“递归”,还提示什么啊?其实,这正是谷歌搜索引擎背后程序员们的“冷幽默”所在:如果你点击了那个提示“递归”,搜索引擎将再次搜索“递归”——相当于自己调用自己——这不正是递归的精髓吗?

或许你懂了,会心一笑,但可能还会疑惑:这也不对啊,所有的递归都有终止条件,如果我们一直点击这个提示词“递归”,查询岂不是会无限循环下去?

放心,你一定不会一直点击下去。因为这个递归的出口正是,查的人终于懂得什么是递归而不再查询。而你就是那个懂得的人。

4.3.2 思维与递归思维

递归(recurse)在计算机领域被广泛应用,它不仅是一种计算方法,更是一种思维方式。科技作家吴军博士认为:递归思维是人与计算机思维最大的差别之一。著名计算机科学家彼得·多伊奇(L. Peter Deutsch)甚至认为,To iterate is human, to recurse divine(迭代是人,递归是神)。

对于计算机从业者来说,想成为顶级人才,在做计算机相关工作时,必须具有递归思维。对于普通人来讲,这种思维方式也很有启发。因此,不论从哪个角度,递归思维都值得我们培养和掌握。

人的常规思维被称为递推(iterate)思维。在中文里,“递推”和“递归”只有一字之差,但在英文世界里,它们的差别可大了去了,可谓“差之毫厘,谬以千里”。

我们先来说说递推。比如小时候我们学习数数,从1、2、3一直数到100,就是典型的递推。类似地,我们在学习过程中循序渐进,如水到而渠成,出发点都是正向的,由易到难,由小到大,由局部到整体。

递推是人类本能的正向思维,于我们而言,可谓熟稔于心。而“递归”则有一定的反常识。

下面我们以计算一个整数的阶乘为例来说明两种思维的差别。如果用人类常用递推方式计算一个整数的阶乘,比如5!=1×2×3×4×5,那么做法是从小到大一个数一个数接连相乘。如果计算10的阶乘(10!),过程也是类似的,即从1乘到10。

在生活中,这种做法不仅合情合理,而且浑然天成。事实上,在中学里学的数学归纳法(利用当n成立时的结论,推导n+1)的方法论就是递推。

为了简单起见,我们还是用前面求阶乘的简单例子来说明递归的原理。计算机是怎么计算阶乘的呢?它是倒着来的。比如要算5!,计算机就把它变成5x4!(即5乘以4的阶乘)。当然,我们可能会质疑,4!还不知道呢!

但没有关系,计算机会采用同样的方法,把4!变成4x3!。至于3!,则用同样的算法处理。最后做到1!时,计算机知道1!=1(这就是递归的终止条件),自此便不再往下扩展了。

接下来,就是倒推回所有的结果。因为由于知道了1!,顺水推舟,就知道了2!,然后可知3!、4!和5!从上面描述的递归过程可以看出,递归的方法论可归结为两步:先从上向下层层展开,再从下到上一步步回溯。

4.3.3 递归调用的函数

你可能会问,计算机为何要这么算?这么算有何优势?答案并不复杂,因为利用递归可以使算法的逻辑变得非常简单。因为递归过程的每一步用的都是同一个算法,计算机只需要自顶向下不断重复即可。

具体到阶乘的计算,无非就是某个数字n的阶乘,变成这个数乘以n-1的阶乘。因此,递归的法则就两条:一是自顶而下(从目标直接出发),二是不断重复。

递归的另一个特点在于,它只关心自己的下一层的细节,而并不关心更下层的细节。你可以理解递归的简单,源自它只关注“当下”,把握“小趋势”,虽然每一步都简单,但一直追寻下去,也能获得自己独特的精彩。

下面我们就以计算阶乘为例,分别使用递推和递归方式实现,见【范例4-7】,读者可体会二者的区别。

【范例4-7】利用递推和递归方式分别计算n!(iterative-recursive.py)。01 #用正向递推的方式计算阶乘

02 def iterative_fact( n ):

03 fact = 1

04 for i in range(1, n + 1):

05 fact *= i

06 return fact

07

08 # 用逆向递归的方式计算阶乘

09 def recursive_fact( n ):

10 if n <= 1 :

11 return n;

12 return n * recursive_fact(n - 1)

13

14 #调用非递归方法计算

15 num = 5

16 result = iterative_fact( num );

17 print("递推方法:{}!= {}".format(num, result))

18 #调用递归方法计算

19 result = recursive_fact(num)

20 print("递归方法:{}!= {}".format(num, result))

【运行结果】递推方法:5!= 120

递归方法:5!= 120

【代码分析】

第02~06行定义了一个递推计算阶乘的函数iterative_fact(),函数内部采用for循环的方式来计算结果。在for循环控制过程中使用了range()函数,由于range的取值区间是左闭右开的,最后一个值取不到,所以在第04行执行了n+1操作。

第09~12行定义一个递归函数recursive_fact,采用递归的方式计算结果。

第17行和第20行用到了Python的格式化输出。在Python中,一切皆对象。用双引号引起来的字符串“递归方法:{}!= {}”,实际上是一个str对象。既然是对象,它就会有相应的方法成员,format()就是用于格式化输出的方法,因此可以通过“对象.方法名”的格式来调用合适的方法。字符串中的花括号{}表示输出占位符,第1个占位符{}用于输出format()函数中第1个变量,第2个占位符{}用于输出format()函数中第2个变量,以此类推。

递归函数的优点在于,定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但正向递推(即循环)的逻辑不如逆向递归的逻辑清晰。

对于递推的实现,这里用到了前面章节中讲到的for循环语句,以1为基数不断循环相乘,最终得出阶乘的结果。而在递归实现的操作中,这里通过对方法本身的压栈和弹栈的方式,将每一层的结果逐级返回,通过逐步累加求得结果。

recursive_fact(5)的计算过程如下。===> recursive_fact (5)

===> 5 * recursive_fact (4)

===> 5 * (4 * recursive_fact (3))

===> 5 * (4 * (3 * recursive_fact (2)))

===> 5 * (4 * (3 * (2 * recursive_fact (1))))

===> 5 * (4 * (3 * (2 * 1)))

===> 5 * (4 * (3 * 2))

===> 5 * (4 * 6)

===> 5 * 24

===> 120

需要注意的是,虽然递归有许多的优点,但缺点也很明显。那就是,使用递归方式需要函数做大量的压栈和弹栈操作,由于压栈和弹栈涉及函数执行上下文(context)的现场保存和现场恢复,所以程序的运行速度比不用递归实现要慢。

此外,大量的堆栈操作消耗的内存资源要比非递归调用多。而且,过深的递归调用还可能会导致堆栈溢出。如果操作不慎,还容易出现死循环。因此读者编写代码过程中需要多加注意,一定要设置递归操作的终止条件。

思考与练习:一道关于递归的面试题(谷歌公司)

(1)有这么一个游戏:有两个人,第一个人先从1和2中挑一个数字,第二个人可以在对方的基础上选择加1或者加2,然后又轮到第一个人,他也可以选择加1或者加2,之后再把选择权交给对方,就这样双方交替地选择加1或者加2,谁先加到20,谁就赢了。对于这个游戏,你用什么策略保证一定能赢?

【案例分析】

如果用正向的递推思维(比如说穷举法),并不容易想清楚,而且还容易漏掉合理的解。但如果用逆向的递归思维,问题的解就非常容易推导出来。我们先从结果出发,如果要想抢到20,就需要抢到17,因为抢到了17,无论对方是加1还是加2,你都可以加到20。而要想抢到17,就要抢到14,以此类推,就必须抢到11、8、5和2。

图4-6 计算一下共有多少种上楼梯的方法

因此对于这道题,只要第一个人抢到了2,他就赢定了。这是因为,无论对方选择加1还是加2,他都可以让这一轮两个人加起来的数值等于5。同样的道理,在当前和为5的基础上,无论对方选择加1或加2,他都能让和向着8进发。以此类推,整个过程都被他牢牢控制,最终的数列之和,毫无悬念地被他锁定在20。

当然谷歌的面试题并非这么简单,如果你答对第一道题,那么紧接着就会有下一道题。

(2)按照上述方法,在不考虑谁输谁赢的情况下,从一开始(以1或2为起点)加到20,有多少种不同的递加过程?比如1,4,7,10,12,15,18,20算一种;2,5,8,11,14,17,20又是一种。那么一共会有多少种这样的过程呢?

【案例分析】

这道题显然并不简单,通过正向的穷举法很难完备遍历。解这道题的技巧还是要使用递归。我们假定数到20有F(20)种不同的路径,那么到达20这个数字,前一步只有两个可能的情况,即从18直接跳到20,或者从19数到20。

由于从18跳到20和从19到20是不同的,因此达到20的路径数量,其实就是达到18的路径数量,加上达到19的路径数量,也就是说,F(20)=F(18)+F(19)。类似地,F(19)=F(18)+F(17)。这就是递推公式。

最后,F(1)只有一个可能,就是1,F(2)有两个可能,要么直接跳到2,要么从1达到2。知道了F(1)=1和F(2)=2,就可以知道F(3)。知道F(3),就可以知道F(4),因为F(4)= F(3)+ F(2),以此类推,一直到F(20)即可。

聪慧如你,你一定看出来了,这就是著名的斐波那契数列,如果我们认为F(0)也等于1,那么这个数列就长成这样:1(F(0)),1,2,3,5,8,13,21,……这个数列几乎按照几何级数的速度增长,到了F(20),就已经是10946了(可利用前面的【范例3-13】来测试)。因此,仅仅靠正向的穷举法,基本上是不可能把所有情况都列举出来的。

上述面试题来自于曾在就职于谷歌公司的吴军博士。吴军博士在分析这道面试题时指出,在数学和计算机上,等价性原则是一个非常重要的原则。很多问题的表象看起来纷繁复杂,但抽丝剥茧之后,其本质是等价的。

比如说,如果一个楼梯有20阶,你每次可以爬一阶歇一会,也可以两阶歇一会,爬到20阶一共有多少种歇息法?这个问题的解,其实和“谁先抢到20”是一样的,也是一个斐波那契数列。

除了前面讲解的技巧,本章涉及的一些思维方式也值得读者注意。从某种程度上来看,递归思维是一种以结果为导向,反向追寻,直到追寻到原点(递归的终止条件)的思维方式,一旦原点问题得以解决,其后的问题都会迎刃而解。

你看看,这是不是和埃隆·马斯克(Elon Musk)等人常说的“第一性原理”思想有着类似之处呢?

本文部分节选自《Python极简讲义:一本书入门数据分析与机器学习》(张玉宏)【摘要 书评 试读】- 京东图书​item.jd.com

(张玉宏著,电子工业出版社,2020年5月出版)。更多理论推导及实战环节,请参阅该书。

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

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

相关文章

XML基本知识(三)

XML语法(3)--属性、声明符号、字符数据、注释 属性&#xff1a;提供元素的附加信息。 属性是元素开始标签的一部分。eg: <para keywords"Napoleon,France,history">...</para>注意: 属性由“名”和“值”组成&#xff0c;中间有“”分隔。值由引号包括…

apache实验报告 linux_Linux实验报告

Linux实验报告-源代码编译安装Apache(Tarball文件安装)实验背景&#xff1a;通常GNU组织提供的程序包都是源代码格式&#xff0c;即将软件的所有源码文件先以tar打包&#xff0c;然后再使用gizp或是bzip2压缩&#xff0c;生成一个.tar.gz或是.tar.bz2结尾的软件包&#xff0c;也…

python合并两个文本文件内容_用Python 将两个文件的内容合并成一个新的文件.

一个文件的内容是: Introduction to Programming, Networking Fundamentals, Internetworking Technologies, Platform Technologies, Information Technology for Users, Computer Forensics, Enterprise Networks, Database Technologies 还有一个是: BN108, MN401, MN503, B…

设备场景函数——72个

说明&#xff1a;本类 API 函数&#xff0c;内容均摘自网络&#xff0c;版权归实际作者所有。 CombineRgn 将两个区域组合为一个新区域 CombineTransform 驱动世界转换。它相当于依顺序进行两次转换 CreateCompatibleDC 创建一个与特定设备场景一致的内存设备场景 CreateDC,Cre…

安卓手机突然很卡_你的安卓手机越来越卡?教你4招轻松解决问题!

原标题&#xff1a;你的安卓手机越来越卡&#xff1f;教你4招轻松解决问题&#xff01;现在我们对于手机的使用时间不会特别的长&#xff0c;所以这个更换的速度就比较快。其实是很多的用户换手机是因为比较喜欢用新的手机体验新的科技&#xff0c;不过也还是有很多的用户不是因…

python 导入自己写的类_python中自己的类不能被导入

我自己编写了一个类 class Settings(): def __init__(self): self.screen_width1200 self.screen_height800 self.bg_color(230,230,230) 然后我想在引用 import sys import pygame from settings import Settings def run_game(): pygame.init() ai_settingsSettings() screen…

关于使用在webforms里使用routing遇到的问题

看了重典的两篇文章 System.Web.Routing入门及进阶 上篇 System.Web.Routing入门及进阶 下篇 仿照其中的进行了操作&#xff0c;发现怎么不都起作用&#xff0c;非常奇怪&#xff0c;搜了好多才找到了解决方案 是在MSDN上找到的&#xff0c;如下&#xff1a; To configure …

python调用父类构造函数需要放在第一句吗_Python继承和调用父类构造函数

这是我正在Python中做的&#xff1a;class BaseClass:def __init__(self):print The base class constructor ran!self.__test 42class ChildClass(BaseClass):def __init__(self):print The child class constructor ran!BaseClass.__init__(self)def doSomething(self):prin…

python千位分隔符_python – 为pandas数据帧中的整数设置千位分隔符

我正在尝试使用{&#xff1a;,}’.格式(数字),如下例所示,格式化pandas数据帧中的数字&#xff1a; # This works for floats and integers print {:,}.format(20000) # 20,000 print {:,}.format(20000.0) # 20,000.0 问题是,对于具有整数的数据帧不起作用,并且在具有float的数…

[导入]【翻译】WF从入门到精通(第十章):事件活动

摘要: 学习完本章&#xff0c;你将掌握&#xff1a;1.使用HandleExtenalEvent活动创建特定的事件处理程序2.在你的工作流中使用Delay活动3.在你的工作流中使用EventDriven活动4.在你的工作流中使用Listen活动5.理解EventHandlingScope活动在活动并发执行的情况下是怎样监听事件…

mysql查询返回xml格式_MySQL数据库查询操作XML的经验分享

mysql里面有内置的操作xml的函数。分别是ExtractValue()和UpdateXML()函数。语法&#xff1a;1EXTRACTVALUE (fiedname, XPathstring);第一个参数&#xff1a;fiedname是String格式&#xff0c;为表中的字段名第二个参数&#xff1a;XPathstring (Xpath格式的字符串) &#xff…

DOM解析原理

DOM解析原理 关键字: xml w3c dom属性和方法用于处理XML文档的DOM元素属性 属性名 描述 childNodes 返回当前元素所有子元素的数组 firstChild 返回当前元素的第一个下级子元素 lastChild 返回当前元素的最后一个子元素 nextSibling 返回紧跟在当前元素后面的元素 no…

wordpress发布模块_如何用WordPress打造出一个类似知乎的问答站点

像打造一个像知乎这样的在线问答社区吗&#xff1f;问答网站非常有意思&#xff0c;而且用户活跃度也高&#xff0c;有很多非常有用的信息。在这篇文章中&#xff0c;我们将向你展示如何在没有任何编程经验的情况下利用WordPress快速打造一个问答类网站。你可以将整个网站做成问…

mysql 密码sha256_MySQL5.6启用sha256_password插件

一、背景&#xff1a;使用MySQL5.6过程中&#xff0c;发现默认的加密插件为mysql_native_password。而sha256_password的安全程度要比mysql_native_password高&#xff0c;尝试切换为sha256_password。二、配置过程&#xff1a;资料&#xff1a;1、从MySQL官网查询到服务器端sh…

Windows 窗体启动和关闭的事件顺序

对于关注对 Windows 窗体应用程序中引发的每个事件按次序进行处理的开发人员来说&#xff0c;事件引发的顺序特别重要。当某种情况需要小心处理事件时&#xff08;如重绘窗体的某些部分时&#xff09;&#xff0c;必须知道事件在运行时的确切引发顺序。本文提供了一些有关在应用…

ftp无法连接虚拟机_一步步编写操作系统4 安装x86虚拟机 bochs

本节内容摘自《操作系统真象还原》&#xff0c;请大家支持正版Bochs下载安装在完成了linux发行版的安装后&#xff0c;现在到了安装bochs的环节&#xff0c;这是我们的操作系统最终的宿主机。由于我的工作是运维&#xff0c;所以练就了任何软件包都要从源码安装的“陋习”&…

accsess转成mysql语句_access数据库转mysql经验分享

ACCESS数据库转换MYSQL数据库的软件1.0版一、 软件介绍&#xff1a;DB2MYSQL是一个可以自动将ACCESS数据库文件转化为对应的SQL代码的软件。可广泛应用于ACCESS数据库转换为MYSQL或其他类型的SQL数据库的软件。DB2MYSQL能够实现可视化操作数据库&#xff0c;转换界面非常简单明…

Using .NET C# LDAP Library(Novell.Directory.Ldap)

说明在这里 http://www.novell.com/coolsolutions/feature/11204.html 此Library需要的Mono.Security.dll 转载于:https://www.cnblogs.com/wingfay/archive/2009/02/05/1384585.html

python语言用什么关键字来声明一个类_Python语言和标准库(第三章:类和对象)...

python如何将函数和数据整合在一起&#xff0c;并且通过一个对象的名称访问它们。 如何和为什么使用类与对象&#xff0c;以及他们如何使编程人员易于多种情形下编写和使用程序。 3.1考虑编程 现在要在python中创建一个对对象的描述&#xff0c;您已有足够的只是获得两个视图。…

mysql主从1594错误_3分钟解决MySQL主从1594错误

3分钟解决MySQL主从1594错误简介Part1:写在最前1594这个错误看起来挺严重的&#xff0c;会提示你binlog文件或者Relay log损坏了,例如binary log is corrupted、relay log is corrupted之类的看起来很吓人是吧&#xff0c;多数是由于掉电引发的&#xff0c;这也说明了机房配备U…