QCon 演讲火热征集中,快来分享技术实践与洞见! 了解详情
写点什么

函数的递归

  • 2020-05-25
  • 本文字数:4231 字

    阅读完需:约 14 分钟

函数的递归

本文节选自张玉宏著的《Python 极简讲义:一本书入门数据分析与机器学习》,由电子工业出版社授权分享。


调用通常发生在彼此不同的函数之间。其实,函数还有一种特殊的调用方式,那就是自己调用自己,这种方式称为函数递归调用。递归,在程序设计中也是一个常用的技巧,甚至是一种思维方式,非常值得我们掌握。

感性认识递归

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


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


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



递归示意图


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


在程序设计中,显然不能出现这种无终止的调用。因此,在编写递归算法时,读者要特别注意,所有递归一定要有终止条件,这又被称作递归出口。如果一个递归函数缺少递归出口,执行时就会陷入死循环。递归出口通常可用 if 语句来设置,在满足某种条件时不再继续,调用某个值,结束递归。


谷歌公司有世界上最聪明的程序员。他们不光聪明,还很有自己的“冷幽默”,别出心裁。比如说,假设你不懂得什么是“递归”,不妨去谷歌搜索一下这个关键词。然后你会发现,除了给出必要的搜索结果,谷歌还给出了一条提示语“您是不是要找:递归”,如下图所示。



谷歌程序员的“冷幽默”


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


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


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

思维与递归思维

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


为了简单起见,我们还是用前面求阶乘的简单例子来说明递归的原理。计算机是怎么计算阶乘的呢?它是倒着来的。比如要算 5!,计算机就把它变成 5×4!(即 5 乘以 4 的阶乘)。当然,我们可能会质疑,4!还不知道呢!但没有关系,计算机会采用同样的方法,把 4!变成 4×3!。至于 3!,则用同样的算法处理。最后做到 1!时,计算机知道 1!=1(这就是递归的终止条件),自此便不再往下扩展了。


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

递归调用的函数

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


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


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


下面我们就以计算阶乘为例,分别使用递推和递归方式实现,大家可体会二者的区别。


【范例】利用递推和递归方式分别计算


01   #用正向递推的方式计算阶乘02   def iterative_fact( n ):  03       fact = 104       for i in range(1, n + 1):05           fact *= i06       return fact07     08   #用逆向递归的方式计算阶乘09   def recursive_fact( n ):10       if n <= 1 :11           return n;12       return n * recursive_fact(n - 1)13     14   #调用递推方法计算15   num = 516   result = iterative_fact( num );17   print("递推方法:{}!= {}".format(num, result))18   #调用递归方法计算19   result = recursive_fact(num)20   print("递归方法:{}!= {}".format(num, result))
复制代码


【运行结果】


递推方法:5!= 120递归方法:5!= 120
复制代码


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


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


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

谷歌公司关于递归的面试题

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


【案例分析】


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


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


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


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


【案例分析】


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


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


最后,只有一个可能,就是 1,有两个可能,要么直接跳到 2,要么从 1 达到 2。知道了,就可以知道。知道,就可以知道,因为,以此类推,一直到即可。


聪慧如你,你一定看出来了,这就是著名的斐波那契数列,如果我们认为也等于 1,那么这个数列就长成这样:这个数列几乎按照几何级数的速度增长,到了,就已经是 10946 了。因此,仅仅靠正向的穷举法,基本上是不可能把所有情况都列举出来的。


上述面试题来自曾就职于谷歌公司的吴军博士。吴军博士在分析这道面试题时指出,在数学和计算机上,等价性原则是一个非常重要的原则。很多问题的表象看起来纷繁复杂,但抽丝剥茧之后,其本质是等价的。比如说,如果一个楼梯有 20 阶,你每次可以爬一阶歇一会,也可以爬两阶歇一会,爬到 20 阶一共有多少种歇息法?这个问题的解,其实和“谁先抢到 20”是一样的,也是一个斐波那契数列。


从某种程度上来看,递归思维是一种以结果为导向,反向追寻,直到追寻到原点(递归的终止条件)的思维方式,一旦原点问题得以解决,其后的问题都会迎刃而解。


图书简介


https://u.jd.com/9WncFq


2020-05-25 10:001964

评论

发布
暂无评论
发现更多内容

Linux-技术专题-Linux命令如何进行查看进程

洛神灬殇

JDK8中的新时间API:Duration Period和ChronoUnit介绍

程序那些事

java8 jdk8 新特性 程序那些事 时间API

开源技术够用了么?我的 NAS 选型与搭建过程

LeanCloud

开源 NAS

5G时代的到来对直播的影响

anyRTC开发者

5G 音视频 WebRTC 直播 RTC

CloudQuery V1.2.0 版本发布

BinTools图尔兹

数据库 sql 编辑器 工具软件

一场关于FLV是否要支持HEVC的争论

wangwei1237

技术文化

「排序算法」图解双轴快排

bigsai

排序算法 快速排序 双轴快排

央视呼吁电商双十一少一些套路:应该严打网店套路营销

石头IT视角

小熊派开发板实践:智慧路灯沙箱实验之真实设备接入

华为云开发者联盟

物联网 IoT 路灯

阿里对Java候选人的面试考察重点,面P7必问(收藏备用)

小Q

Java 学习 架构 面试 高并发

TensorFlow 篇 | TensorFlow 数据输入格式之 TFRecord

Alex

tensorflow keras dataset tfrecord

架构师训练营 W03 总结

Geek_f06ede

架构师训练

给萌新HTML5 入门指南(二)

葡萄城技术团队

颠覆!阿里5位P8大佬分享进阶王者500修炼手册,修三门课程

996小迁

Java 程序员 架构 面试

测试攻城狮必备技能点!一文带你解读DevOps下的测试技术

华为云开发者联盟

敏捷开发 测试 瀑布流

如何在面试中解释关键机器学习算法

计算机与AI

学习 数据科学

帮助企业摆脱困境,名企归乡工程师:能成功全靠有它!

Learun

敏捷开发 快速开发 企业开发 企业应用

23张图!万字详解「链表」,从小白到大佬!

王磊

Java 数据结构与算法

第一届“多模态自然语言处理研讨会”精彩回顾(免费获取PPT)

京东科技开发者

人工智能 自然语言处理

深度解读智能推荐系统搭建之路 | 会展云技术揭秘

京东科技开发者

人工智能 推荐系统

甲方日常 44

句子

工作 随笔杂谈 日常

Linux高级编程常用的系统调用函数汇总

哒宰的自我修养

Linux 线程 网络编程 进程 MySQL数据库

环球易购数据平台如何做到既提速又省钱?

苏锐

大数据 hdfs S3 CDH 成本优化

接口测试用例编写和测试关注点

测试人生路

接口测试 测试用例

英特尔独显终于来了!锐炬®Xe MAX为非凡S3x带来设计师级创作体验

E科讯

腾讯内容首发:分布式核心原理解析笔记+分布式消息中间件实践笔记PDF版

Java架构追梦

Java 架构 面试 分布式 消息中间件

【涂鸦物联网足迹】物联网基础介绍篇

IoT云工坊

人工智能 云计算 物联网 云平台 AIOT

推进AI融合 2020 LF AI & DATA DAY(AI开源日)即将召开

网易云音乐基于 Flink + Kafka 的实时数仓建设实践

Apache Flink

flink

架构师训练营 W03 作业

Geek_f06ede

架构师训练

Worktile旗下智能化研发管理工具PingCode 宣布25人以下免费

爱吃小舅的鱼

团队管理 程序人生 敏捷开发 研发管理 研发管理工具

函数的递归_AI&大模型_张玉宏_InfoQ精选文章