原文转载自「刘悦的技术博客」https://v3u.cn/a_id_186

“从来如此,便对么?”,鲁迅先生在《狂人日记》中借狂人之口在月光下发出的质疑与呐喊,是的,从来如此,一般人的思维模式就是从来如此,以高数为例子,我们大抵都是先从数分、线代、解几去学泛函、抽代、拓扑等,其实就是按照标准路子来,这样做理论上可以增加对已学知识的理解程度,并对某些数分、线代中的问题看清其本质有所帮助。数学归纳法其实就是一种迭代(iteration),从一个简单的起点,推广到一般情况。而递归(recursion),则是一种反人类的逆向思维模式,作为研发人员,掌握这种反常识的思维逻辑是非常必要的,这里我们以一个推理故事为开端:

在一个秋意绵绵的雨夜,日本警视厅搜查课警司古畑任三郎和其助手今泉驾着小汽车行驶在山路上,突然,汽车抛锚了,无奈之下,古畑只好下车徒步寻求帮助,不远处一所别墅的灯光吸引了他,古畑敲响了别墅的大门,打开门的是美女漫画家千奈美,这里正是她的私人别墅,千奈美得知来的是一位刑警,便告诉了古畑一个可怕的事实,在她私人别墅的仓库里,有一具尸体,这具尸体不是谁,正是漫画出版社的编辑畑野先生,死者面朝下趴在地面上,尸体的额头上有碰伤的痕迹,周围散落着一些稿纸。他把其中的一张稿纸紧紧捏在手里,而纸上却没留下任何遗言之类的东西,仅仅是一张白纸,而恰巧,旁边就有一只打开笔帽的钢笔,在简单勘察现场之后,古畑问起千奈美和畑野的关系,千奈美说他们不过是工作上的关系。但因为经常到这儿来商讨出版事宜,所以她给了畑野一把钥匙。

随后,千奈美微笑解释到,畑野先生可能死于意外,仓库的门一旦关上,从里面是打不开的。畑野的死可能就是因为不小心把自己关在了里面,死因是由于缺氧窒息,她自己已经一个多月没有来到这所别墅了,也正是刚刚,她才发现了畑野的尸体。古畑则不这么认为,他怀疑这是一起谋杀。因为死者头部有伤痕,流了血。千奈美认为不可能,因为门是关上的,没有钥匙打不开,只有她和畑野先生有别墅的钥匙,别人不可能进来打破畑野的头,而自己又已经一个多月没有来过了,古畑此时起了疑心,他认为千奈美的说法不合常理。好比有一罐饼干,我们打开后发现有一块被人咬了一口,通常情况下,我们都会推测是有人先咬了饼干再盖上盖子的。聪明的古畑发现千奈美言行有疑点,于是又折回仓库,在死者的口袋里发现几张三天前的购物发票,据此,古畑分析道:死者肯定知道凶手的名字,因为三天前死者和凶手一同来到别墅,而死者身边肯定藏着揭穿凶手真相的线索,为什么死者会在这么多的稿纸中,紧紧抓住这一张空白的稿纸呢?明明有纸有笔,却什么都没有写。他到底想表达什么呢?古畑费解地思考着。
此时,雷电交加,屋内突然停电了。千奈美忍不住叹息道:“今晚,真是糟糕透了!”,雷雨交加的黑夜、幽深的别墅、绝美的少女、漆黑的仓库以及恐怖的尸体,这一切显得那么奇特和诡秘,千奈美拿出蜡烛,发现古畑在看书,古畑赞叹千奈美的书真是一部杰作啊。夜深了,两个人都饿了,他们来到储藏室,女主不假思索拿出鸡蛋想做蛋羹汤。细心的古畑怀疑到:你一个多月没来过了,那么这里的鸡蛋真的还能吃吗?
回到别墅,古畑对千奈美说道:“请原谅我得说出事实的真相,三天前,是千奈美小姐和畑野先生一起来到的别墅吧?”,“我上次来这里是一个月前的事情了,”,千奈美面无表情,“你没有说实话。请问你是怎么知道冰箱里的鸡蛋还是新鲜的?你最近一次来这里不是一个月前,也就是说你是三天前把鸡蛋放进冰箱的。在我的想象中,不知世间冷暖的美女漫画家和花花公子般的出版社编辑,你们之间发生了什么我不清楚,但其中一人终于发现对方只是逢场作戏,于是进行了残酷的报复。”,“古畑先生,这真是一个俗套的故事呢”,千奈美笑了笑,“凶手为什么会打破死者的头呢?如果让别人以为这是一起意外的话,凶手是绝不会这么做的。对吗?可是被害人却被打伤了,这一点很矛盾。于是我仔细考虑了一下,把谋杀伪装成意外的人和故意留下他杀证据的人会不会不是同一人呢?”,
“难道凶手有两个人吗?”,“凶手只有一个,我们其实忽略了非常关键的一点,就是这张白纸,死者确实有留下了线索,只是我们还没有发现罢了。线索就留存在这张纸上。我们换位思考一下,试想畑野先生当时的感受,他无论如何都想留下凶手的名字,但不管他如何想办法写下凶手的名字,一旦要是被凶手先发现的话,就会被销毁,他非常明确这一点,最先发现尸体的一定是凶手,因为凶手把他关在仓库里的,于是呢,他冥思苦想,先拿起一张纸,再把笔帽摘下,而就是什么都没有写,你懂了吗?在能够写下凶手名字的情况下,他什么都没有写,这就是他所留下的线索。他想传达的意思就是‘无论我写什么都是没用的’”。

“那他头上的伤呢?怎么解释?“

“这也是畑野先生留下的线索之一。因为他不管怎么想办法留下线索,如果万一案子被当作意外处理了,那么一切都没有意义了。所以他无论如何都要留下这是他杀的证据,因此,他自己给自己的脑袋来了一下。”
是的,一张什么都没有写的白纸就是亡者留下的线索。这张白纸在默然无声地诉说:杀死我的凶手,就是最先发现我尸体的人。
这个故事本身并不复杂,由爱生恨的狗血谋杀案,但是故事的核心却是一张白纸,亡者留下的唯一线索,这个线索被古畑发现了,亡者不是不想说出凶手的名字,而是不能说,这里古畑运用的实际上就是基于逆向思维的递归逻辑。

那么,从代码层面上来看,递归可以帮我们解决什么问题呢?我们以高斯求和为例子,所谓高斯求和,即在一个阈值范围内,将所有的整数相加求和的算术题,如果使用迭代逻辑:

def sum_number(n):
total = 0
for i in range(1, n+1):
total += i
return total

调用方法:

print(sum_number(5))  

liuyue:mytornado liuyue$ python3 "/Users/liuyue/wodfan/work/mytornado/excel_test.py"
1
3
6
10
15
15
liuyue:mytornado liuyue$

可以看到,迭代思想的本质是递增遍历,按照顺序将元素一个一个的累加,并不难理解,接着我们来试试递归的解法:

def sum_number(n):
if n <= 0:
return 0
return n+sum_number(n-1)

可以看到,当我们使用递归设计程序的时候,我们从最终结果入手,即要想求得sum_number(5),电脑会把这个计算拆解为求得sum_number(4)的运算,以及sum_number(4)加上5的运算。以此类推,直到拆解为sum_number(1)的运算,就触发终止条件,也就是if结构中n<=0时,返回一个具体的数0。尽管整个递归过程很复杂,但在编写程序时,我们只需关注初始条件、终止条件及衔接,而无须关注具体的每一步。

递归思维是自顶而下的,我们做事的时候可以先从整体上考虑。先明确需要达到的大目标,而不是一开始就在细节上较真,这其实也是系统论的思想。很多初入职的程序员,在没有清楚项目整体功能架构的情况下,就急于写代码,最终往往导致多次返工,事倍功半,不过使用Python设计递归程序需要注意栈溢出的问题,如果递归深度超出1000层就会报错,所以需要单独设置递归深度:

import sys
#更改递归深度为1百万
sys.setrecursionlimit(1000000)

搞清楚递归的简单思路,让我们来试一试进阶的操作:尾递归。尾递归相对传统递归,其是一种特例。在尾递归中,先执行某部分的计算,然后开始调用递归,所以你可以得到当前的计算结果,而这个结果也将作为参数传入下一次递归。这也就是说函数调用出现在调用者函数的尾部,因为是尾部,所以其有一个优越于传统递归之处在于无需去保存任何局部变量,从内存消耗上,实现节约特性:

def tail_sum(n,result=0):
if n==0:
return result
else:
return tail_sum(n-1, result+n)

传统递归的解题步骤:

5+sum_number(4)
5+(4+sum_number(3))
5+(4+(3+sum_number(2)))
5+(4+(3+(2+sum_number(1))))
5+(4+(3+(2+1)))
15

每一次递归,程序会将计算结果存储在内存中,在递归过程中做累加,直到走向递归出口,尾递归则是通过传参将累加结果进行递归传递:

tail_sum(5,0)
tail_sum(4,5)
tail_sum(3,9)
tail_sum(2,12)
tail_sum(1,14)
tail_sum(0,15)

如果递归深度非常大的情况下,就可以大量节约内存成本。

相关视频攻略,请移步:

https://www.bilibili.com/video/BV17A411g7Rg

综上,熟练运用递归需要注意以下三个特点:

1.问题本身可以拆分成更为简单的子问题,而子问题可以通过相同的方法解决。

2.解题需要提前考虑程序出口,否则会掉进递归死循环的陷阱。

3.递归并不是非常高效的算法,大数量级的问题需要尾递归的参与。

结语:掌握递归逆向思维的人,在解决一些棘手的问题时,往往能够另辟蹊径,善于运用资源,找到解决问题的巧妙办法,就像文章开篇的古畑刑警一样。当我们面对生活中、社会上的种种问题,是否能够想象出鲁迅先生那句“从来如此,便对么”的呐喊。“从来如此”或许对,或许不对。但是作为当代人,自省吾身,与君共勉。

原文转载自「刘悦的技术博客」 https://v3u.cn/a_id_186

人理解迭代,神则体会递归,从电影艺术到Python代码实现神的逆向思维模式的更多相关文章

  1. 深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点

    深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点 2011-12-28 23:00 by 汤姆大叔, 139489 阅读, 119 评论, 收藏, 编辑 才华横溢的 ...

  2. 深入理解 GIL:如何写出高性能及线程安全的 Python 代码

    深入理解 GIL:如何写出高性能及线程安全的 Python 代码 本文由 伯乐在线 - 郑芸 翻译.未经许可,禁止转载!英文出处:A. Jesse.欢迎加入翻译组. GIL对多线程的影响:http:/ ...

  3. VsCode写Python代码!这代码简直和大神一样规范!太漂亮了!

    VsCode写Python代码!这代码简直和大神一样规范!太漂亮了!    转 https://www.jianshu.com/p/636306763d89 VsCode虽然没有Pycharm的功能齐 ...

  4. 99%的人理解错 HTTP 中 GET 与 POST 的区别

    转自:http://www.oschina.net/news/77354/http-get-post-different GET和POST是HTTP请求的两种基本方法,要说它们的区别,接触过WEB开发 ...

  5. 如何写好PPT,什么样的PPT容易被人理解记住

    PPT一般是用于讲解性的行为而存在,那如果写好PPT呢?如果写好,这个完全要取决于你所面向的目标读者,是用于学术行为呢?还是用于商业行为.面对不同的目标群体,有不同的策略.但是无论面向群体是谁我们都有 ...

  6. Spring Cloud-个人理解的微服务演变过程(一)

    最初架构 说明:最初我们架构是垂直的 所有功能都在一个项目里面 随着业务和用户的增长 原来一台服务器已经不能支撑现有的请求数 这个时候我们就需要部署多台服务器 集群模式 说明:我们使用nginx做代理 ...

  7. 从零开始一起学习SLAM | 理解图优化,一步步带你看懂g2o代码

    首发于公众号:计算机视觉life 旗下知识星球「从零开始学习SLAM」 这可能是最清晰讲解g2o代码框架的文章 理解图优化,一步步带你看懂g2o框架 小白:师兄师兄,最近我在看SLAM的优化算法,有种 ...

  8. 递归,装饰器,python常用内置方法

    **递归**        def calc(n):            print(n)            if int(n / 2) == 0:  条件判断                r ...

  9. 一道仅有7人通过的超5星微软比赛题目-------解题思路&优秀代码分享,邀你来“找茬儿”

    6月23日英雄会平台发布了一道难度为超5星的微软比赛题目,截止活动结束共有300多名编程爱好者参与线上答题,而最终通过者仅有7人,通过率仅为2%.为什么成绩如此出人意料?是因为题目的英文描述难以理解? ...

随机推荐

  1. hive从入门到放弃(六)——常用文件存储格式

    hive 存储格式有很多,但常用的一般是 TextFile.ORC.Parquet 格式,在我们单位最多的也是这三种 hive 默认的文件存储格式是 TextFile. 除 TextFile 外的其他 ...

  2. linux篇-rpm包安装mysql数据库

    3.1上传以下两个rpm包到服务器上 MySQL-server-5.6.27-1.el6.x86_64.rpm MySQL-client-5.6.27-1.el6.x86_64.rpm 3.2卸载一个 ...

  3. Git标签用法

    我们通常会在项目开发到一定阶段时给代码打上标签. 1.Git查看所有标签及其描述信息 git tag -l -n 2.Git创建标签 创建标签并添加描述信息 git tag -a v1.0.0 -m ...

  4. Flask表单验证

    学习内容:①判断请求方式(request.method) from flask import Flask,render_template,request app = Flask(__name__) @ ...

  5. 拭目以待!JNPF .NET将更新.NET 6技术,同时上线 3.4.1 版本

    2022年5月30日,福建引迈即将上线JNPF开发平台的.NET 6版本,在产品性能上做了深度优化,且极大的提升了工作效率,加强了对云服务的改进升级,全面提升用户的使用体验. JNPF是一个以PaaS ...

  6. 在 .NET 中使用 FixedTimeEquals 应对计时攻击

    计时攻击 在计算机安全中,计时攻击(Timing attack)是旁道攻击 (Side-channel attack) 的一种,而旁道攻击是根据计算机处理过程发出的信息进行分析,包括耗时,声音,功耗等 ...

  7. 2021.04.24【NOIP提高B组】模拟 总结

    2021.04.24[NOIP提高B组]模拟 总结 T1 题意:有一圈数.两两之间有加法或乘法操作, 问你开始断掉那条边使得剩下的序列经过某种操作后的值最大 看上去是个区间 dp .然后直接断环成列, ...

  8. 国内外组态软件对比分析(InTouch、WinCC、iFix、iNeuOS)

    在我国自动化控制领域应用较广泛的工业自动化组态软件有Wonderware公司InTouch.西门子公司Wincc.GE公司iFix.国内也有一些传统组态软件厂商,使用的功能和形式基本上十分类似,受当时 ...

  9. 关于kali安装输入法

    之前老是被kali大小写输入恶心坏了,正好看到一篇文章写kali安装搜狗输入法的,虽然不需要输入中文,但是英文输入就很方便了. 一.切换root用户登录 1.sodu su切换为root权限 2.pa ...

  10. Error: $injector:modulerr Module Error

    Failed to instantiate module app due to://实例化失败 Error: [$injector:modulerr] http://errors.angularjs. ...