SICP 习题 (1.9) 解题总结
SICP 习题 1.9 开始针对“迭代计算过程”和“递归计算过程”,有关迭代计算过程和递归计算过程的内容在书中的1.2.1节有详细讨论,要完成习题1.9,必须完全吃透1.2.1节的内容,不然的话,即使从网上找来答案看也不能理解其中的真谛。
书中1.2.1节是通过阶乘作为样例来讲解的,可能是因为作者们都是一些天才,所以他们都很喜欢使用数学样例,作为凡人的我们要理解他们在讲什么就需要先理解数学,真的是比较痛苦。
当然,阶乘还算不难啦,看完样例后要理解“迭代计算过程”和“递归计算过程”才是比较麻烦的事情。
首先我们先要明白,“递归计算过程”和“递归过程”不是一回事。
递归过程(或者叫递归函数比较容易区分)是指一个会调用自身的过程,就像下面这样的过程:
(define (call-myself x)
(call-myself (+ x 1))
我们就把call-myself这样的过程称之为“递归过程”,当然,上面的递归过程会出现无限递归调用,这个我们先不管它。
有趣的是,上面这个“递归过程”的“计算过程”并不是“递归”的,也就是说上面这个“递归过程”的计算过程不是一个“递归计算过程”。
虽然有那么一点拗口,但是仔细看还是可以看明白的,就是我们在考察的是一个“递归函数”的“计算步骤”是否具有“递归”属性。
那么,什么样的“计算步骤”才算具有“递归”属性呢?就是说,什么样的计算过程才算是“递归计算过程”呢?
按书上的说法,在展开阶段构造起一个推迟执行的操作链条的计算过程就是“递归计算过程”。
对于我们上面提到的call-myself过程,如果我们让call-myself在x大于100的时候不再进入递归过程,那么当x大于100时一切就结束了,不需要再进行其它什么计算,只需要不断退出过程就好了。
比如将call-myself改成这样:
(define (call-myself x)
(if (> x 99)
x
(call-myself (+ x 1))))
其中call-myself过程会不断调用自己,同时x=x+1,当x>99时,过程返回x(这时x的值是100),然后这个100就不断返回给上一层过程,一直返回到最初的call-myself。
这里没有任何的“推迟计算”的操作。
如何让call-myself过程的计算过程变成是“递归计算过程”呢?我们只要对过程的返回值做一些操作就可以了,比如将过程改成这样。
(define (call-myself x)
(if (> x 99)
x
(+ (call-myself (+ x 1)) 2))
注意call-myself过程的返回值会被执行加2的操作,然后继续返回给上一级过程。
在这里,每一次调用call-myself,就有一个“加2”的操作在等着执行,这个“加2”的操作在call-myself返回之前是无法执行的,必须等call-myself返回后才知道对什么数执行“加2”的操作。就是说,在call-myself的展开阶段,系统构造起一个推迟执行的“加2”的操作链条。
简单说,如果递归调用结束之后一直返回的,应该不是“递归计算过程”,它们被称之为“迭代计算过程”,而递归调用结束后需要对返回值再做一些操作的是“递归计算过程”。
这里之所以对“递归计算过程”和“迭代计算过程”进行详细的讨论和区分,是因为“递归计算过程”和“迭代计算过程”的处理可以有很大的差别,这一点在书中还有更详细的讨论。
如果看到这里,你还没有完全搞清楚“递归调用过程”和“迭代计算过程”的差别的话,你可能和我一样是一个数学恐惧者,看着不同的数学符号就发怵。
其实我们可以用一个和数学无关的样例来说明。
那就是“从前有座山,山上有个庙,庙里有个老和尚,老和尚在讲故事:'从前有座山,山里有座庙,庙里有个…'”那个故事。
学编程学到递归调用的时候,大概很多中国程序员都会想到上面那个故事,这是一个典型的递归调用。
把上面的故事写成Lisp过程大概是这样的:
(define (老和尚讲故事)
(从前有座山)
(山里有座庙)
(庙里有个老和尚)
(老和尚讲故事))
如果你是一个追求逻辑完整性的程序员老爸,同时希望这个故事可以讲完,你讲着讲着发现你家小孩睡着了,你可以这样:“...庙里有个老和尚,啊哈,这次这个老和尚不会讲故事”,于是故事嘎然而止,你还需要做什么吗?不需要了,虽然有极端追求完美的程序员会默默地退出一层一层的递归调用。
现在版本的Lisp过程应该是这样的:
(define (老和尚讲故事)
(从前有座山)
(山里有座庙)
(庙里有个老和尚)
(if (小孩睡着了)
(这次这个老和尚不会讲故事)
(老和尚讲故事)))
这个过程的计算过程是“迭代计算过程”,你可以肆无忌惮地讲到任意多的老和尚,一旦小孩睡着了,你就可以用一个不会讲故事的老和尚结束你的故事。
如何把这个故事变成一个“递归计算过程”呢?稍微改一下过程,改成这样:
(define (老和尚讲故事)
(从前有座山)
(山里有座庙)
(庙里有个老和尚)
(if (小孩睡着了)
(这次这个老和尚不会讲故事)
(老和尚讲故事))
(老和尚喝了口水))
就是每个老和尚讲完故事都需要喝口水,那么你的噩梦就开始了,你在开始讲故事的时候就要记着,现在讲到第几个老和尚了,讲了几个老和尚就有几个老和尚在等着喝水呢!
你的故事会变成这样:“..庙里有个老和尚,啊哈,这次这个老和尚不会讲故事)老和尚喝了口水)老和尚喝了口水)......)”
于是你就会发现,相对于“递归计算过程”,“迭代计算过程”是多么可爱,多么简单。其实,这也是程序解释器更喜欢“迭代计算过程”的原因。
其实,如果你够无聊的话,还可以让故事变得更加复杂:
(define (老和尚讲故事)
(从前有座山)
(山里有座庙)
(庙里有个老和尚)
(if (小孩睡着了)
(这次这个老和尚不会讲故事)
(老和尚讲故事))
(老和尚喝了口水)
(可恶的老和尚又想讲故事啦!)
(老和尚讲故事))
这是一个树形递归计算过程,你需要一张很大的纸来记录你目前讲到故事的哪一部分了。
好,讲到这里你应该明白什么是“递归计算过程”,什么是“迭代计算过程”了,如果还不明白的话请从头再读一遍。
既然明白了两者的差别,解答习题1.9就变的很简单了
下面这个过程的计算方式是“递归计算过程”,因为其中的+过程返回后还需要继续执行inc操作,每调用一次+过程,就有一个inc过程等着执行:
(define (+ a b)
(if (= a 0)
b
(inc (+ (dec a ) b ))))
下面这个过程的计算方式是“迭代计算过程”,因为其中的+过程返回后就没什么操作需要执行了:
(define (+ a b )
(if (= a 0)
b
(+ (dec a) (inc b))))
SICP 习题 (1.9) 解题总结的更多相关文章
- SICP 习题 (1.13) 解题总结
SICP习题1.13要求证明Fib(n)是最接近φn/√5 的整数,其中φ=(1+√5)/2 .题目还有一个提示,提示解题者利用归纳法和斐波那契数的定义证明Fib(n)=(φn - ψn) / √5 ...
- SICP 习题 (1.7) 解题总结
SICP 习题 1.7 是对正文1.1.7节中的牛顿法求平方根的改进,改进部分是good-enough?过程. 原来的good-enough?是判断x和guess平方的差值是否小于0.001,这个过程 ...
- SICP 习题 (1.14)解题总结
SICP 习题 1.14要求计算出过程count-change的增长阶.count-change是书中1.2.2节讲解的用于计算零钱找换方案的过程. 要解答习题1.14,首先你需要理解count-ch ...
- SICP 习题 (1.8) 解题总结
SICP 习题1.8需要我们做的是按照牛顿法求平方根的方法做一个求立方根的过程. 所以说书中讲牛顿法求平方根的内容还是要好好理解,不然后面这几道题做起来就比较困难. 反过来,如果理解了牛顿法求平方根的 ...
- SICP 习题 (1.10)解题总结
SICP 习题 1.10 讲的是一个叫“Akermann函数”的东西,去百度查可以查到对应的中文翻译,叫“阿克曼函数”. 就像前面的解题总结中提到的,我是一个数学恐惧者,看着稍微复杂一点的什么函数我就 ...
- SICP 习题 (1.41)解题总结
SICP 习题1.41 看似和周边的题目没有关系,突然叫我们去定义一个叫double的过程,事实上这道题的核心还是高阶函数. 题目要求我们定义一个过程double,它以一个过程作为參数,这个作为參数的 ...
- SICP 习题 (2.10)解题总结: 区间除法中除于零的问题
SICP 习题 2.10 要求我们处理区间除法运算中除于零的问题. 题中讲到一个专业程序猿Ben Bitdiddle看了Alyssa的工作后提出了除于零的问题,大家留意一下这个叫Ben的人,后面会不断 ...
- SICP 习题 (2.7) 解题总结 : 定义区间数据结构
SICP 习题 2.7 開始属于扩展练习,能够考虑不做,对后面的学习没什么影响.只是,假设上面的使用过程表示序对,还有丘奇计数你都能够理解的话,完毕这些扩展练习事实上没什么问题. 习题2.7是要求我们 ...
- SICP 习题 (2.6) 解题总结:丘奇计数
SICP 习题 2.6 讲的是丘奇计数,是习题2.4 和 2.5的延续. 这里大师们想提醒我们思考的是"数"究竟是什么,在计算机系统里能够怎样实现"数".准备好 ...
随机推荐
- 自定义VS的ItemTemplates 实现任意文件结构
上一篇说到重写IHttpHandler实现前后端分离,这次说一下如何建立一个如下文件结构. VS建立webform时是根据模板来的.C#的模板目录如下: F:\Program Files (x86)\ ...
- Socket与TcpClient的区别(转载)
Socket和TcpClient有什么区别 原文:http://wxwinter.spaces.live.com/blog/cns!C36588978AFC344A!322.entry 回答: &qu ...
- 全世界最详细的图形化VMware中linux环境下oracle安装(一)【weber出品必属精品】
安装流程:前期准备工作--->安装ORACLE软件--->安装升级补丁--->安装odbc创建数据库--->安装监听器--->安装EM <前期准备工作> 安装 ...
- 正则表达式,Regex类
C#regex是正则表达式类用于string的处理,查找匹配的字符串.1,先看一个例子Regex regex=new Regex(@”OK“)://我们要在目标字符串中找到"OK" ...
- USB联机线编程接口(API)
USB联机线编程接口(API) 2013-10-19 本页面的文字允许在知识共享 署名-相同方式共享 3.0协议和GNU自由文档许可证下修改和再使用. 关键字:USB隔离线.USB点对点通讯.USB通 ...
- Mysql存储过程分析
为了搞明白为什么mysql的存储过程是高效的,我们需要理解mysql的执行流程是什么,当输入sql语句之后,mysql会先进行sql语句语法正确性检查,然后再进行编译,然后才执行,最后把结果返回.如下 ...
- JS中的== 、===的用法和区别。
JS中的== .===的用法和区别.[转] == 和 != 比较若类型不同,先偿试转换类型,再作值比较,最后返回值比较结果 . 而 === 和 !== 只有在相同类型下,才会比较其值 ======= ...
- PHP获取文件后缀名的三种方法
如下: <? PHP获取文件后缀名的几种方法1: function get_file_type($filename){ $type = substr($filename, strrpos($fi ...
- AIX下解决POWERHA的脑裂问题
一.安装创建并发vg时必需的软件包clvm包,该包安装.升级.后必须重启os clvm包的描述:Enhanced Concurrent Logical Volume Manager 软件包在aix61 ...
- [php] PHP创建指定目录和文件
前几天看到有人问PHP环境下如何创建文件到指定目录下,正好自己最近在学习,经过一翻测试,终于出结果了,贴出来与大家分享. 目录结构: 代码所在的文件wwwroot/mydir/test/test.ph ...