递归的故事

先讲一个故事,这个故事的名字就叫“递归”,故事的内容很简单:

“从前有个小孩叫TT,他最喜欢听‘递归’的故事”。

这个故事比它看上去的要长,因为它实际上是这个样子:

“从前有个小孩叫TT,他最喜欢听

“从前有个小孩叫TT,他最喜欢听

“从前有个小孩叫TT,他最喜欢听

“从前有个小孩叫TT,他最喜欢听

……

的故事”

的故事”

的故事”

的故事”


 

什么是递归(Recursion)?这就是递归,非常不正式的说,递归就是“自己里面又有自己”。同时,递归又是嵌套,各种各样的嵌套。某种行为被相似的过程打断,进入到下一层……然后回来。


 

递归过程和另一个过程迭代(Iteration)似乎容易混淆,实际上举个查阅文献和顺序阅读的例子就能区分这两个概念。

查阅文献的行为就是递归的。想象一下,现在正在阅读一篇论文,遇到了一个引用的概念,为了弄明白这个概念,转而去查阅被引用的论文;被引用的论文又引用了其他论文的概念,于是又去查阅另一篇论文……终于有某一篇论文不再引用其他论文而是自己做出完整的解释,那么查完这篇论文就返回到上一篇论文……最后回到最初的阅读的论文。

 

顺序阅读过程的描述就非常简单了,这是一个迭代的过程,读完一篇再去读另一篇。

在查阅文献的例子中,递归有两个明显的特点:压入(Push)和弹出(Pop)。从一篇论文转到被引用的论文的过程就像是压入到更深的层次;而从被引用论文回到最初阅读的论文,这个过程就像(从被引用论文)弹出。在这么一个过程中,每一层就是一个现场,既要进得去又要出得来,就需要记住每一层的信息。在计算机世界里,有一个专门用来保存现场的数据结构:栈(Stack)。

计算机同时只能兼顾一个现场,因此需要栈保存每一层的信息。相比之下我们人似乎高明得多,虽然大脑中没有一个显式的栈,但我们却可以轻松的把握压入和弹出的过程。



 

很多程序员(包括以前的我)似乎视野受限于程序里的递归,忽视了其他范畴的递归。现实世界里的递归比程序员们想象的要多得多。

事务:假设你正在看新闻联播正在播放胡总视察基层工作,突然插播一条来自美国的现场报道;这条插播还没完,又插播一条来自叙利亚的现场报道……然后回到美国的报道;美国报道结束后,胡总又出现在屏幕上。

数学:1,1,2,3,5,8,13,21,34,55,89……一个大家再熟悉不过的例子,来自意大利数学家Leonardo Pisano

语言:一个句子由主谓宾构成;这个句子的主语也可以是一个句子,这个句子的主语由主谓宾构成;这个句子的主语的主语也可以是一个句子,这个句子的主语的主语由主谓宾构成……

物理:从宇宙的视角看,每个星球可以看做一个质点;从地球的角度看,每个生物可以看做一个质点;从生物的角度看,每个细胞可以看做一个质点;从细胞的角度看,每个分子可以看做一个质点;从分子的角度看……

从某种角度看,这个世界就是递归的,所以在你知道递归的那一天,你可以大声地说:“我发现世界的本质啦哈哈哈哈哈哈哈哈哈,这个世界的本质就是‘我发现世界的本质啦哈哈哈哈哈哈哈哈哈……’”



 

 

 

现在回到计算机世界的递归,也即我们程序员需要的:程序的递归。

注意到递归过程名字内容的区别,一个递归“叫什么”和“是什么”既有联系又有区别。而把“叫什么”转换成“是什么”的过程,就是求值/展开的过程。如果一个行为的内容(是什么)中又包含了这个行为的名字(叫什么),那么对这个行为的名字进行展开,递归也就形成了。递归的特征概括起来不过四个字,“实中有名”,它的本质是简单的,但大道至简,简单的东西时常蕴含的惊人的威力。

 

George Pólya说,了解一个问题,我们有五个好朋友:WhyWhatWhereWhenHow。我也特别想跟这几个好朋友打交道,所以接下来首先回答第0个问题。

0我们程序员为什么需要递归?(Why

答:我们经常会碰上一类问题,这类问题有这样的特性:从未知数一步到达解非常困难甚至是不可能的,但是把它分解成更简单的子问题却不难,而子问题与原问题又刚好有相似的结构。原问题到子问题的过程逼近解,由于递归的作用,这个逼近的过程又刚好还是自发的。对于这种问题,只需描述出从原问题到子问题的转换,就可以解了。


 

Why的回答针对所有递归问题,然而具体问题具体分析就得回答下面三个问题:

1.每一次递归的相似行为是什么?(What

2.在何处发生递归?(Where

3.递归何时终止?(When

 

What: 由于问题的每一级都具有相似的结构,所以解也应该具有相似的行为。这个行为通常就是递归函数的主要函数体,即一个问题向解逼近的过程,也是每一次递归的副本。回答这个问题至关重要,也非常考验程序员归纳程序行为的能力。

Where: 一个过程的名字出现在它的内容中,才会形成递归。名字出现的地方由问题结构所决定,通常分为尾递归和非尾递归。

When: 注意递归的威力非常大,对于正常应用我们通常要限制它的威力,即让递归在某个我们需要的层次上停下来。每个递归程序,都必须决定程序的出口。

 

这三个问题,每一个都关乎程序的正确性。如果正确地回答了一个递归程序的WhatWhereWhen,这个递归就基本算是完成了。


 

 

最后一个问题回到起点。

4. 如何识别一个问题可以被递归解决?(How

答:有些递归很明显,另外一些则不然。一个问题具有以下三种性质之一,即可用递归解决:

(1)数据的定义是按递归定义的

(2)问题解法按递归算法实现  

(3)数据的结构形式是按递归定义的

 

——TT

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值