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的延续. 这里大师们想提醒我们思考的是"数"究竟是什么,在计算机系统里能够怎样实现"数".准备好 ...
随机推荐
- 如何单独编译Android源代码中的模块
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6566662 第一次下载好Android源代码工 ...
- C#用注册表开机自动启动某某软件
代码如下: public static void chkAutoRun(bool isRun) { if (isRun)//开机自动启动 { try { RegistryKey runKey = Re ...
- 执行curl -sSL 提示curl: (35) SSL connect error
今天,添加容器节点报错,执行如下 curl -sSL https://shipyard-project.com/deploy| ACTION=node DISCOVERY=etcd://192.168 ...
- sqlserver中的rowversion
rowversion 公开数据库中自动生成的唯一二进制数字的数据类型.rowversion通常用作给表行加版本戳的机制,存储大小为8字节.rowversion数据类型只是递增的数字,不保留日期或时间. ...
- Asp.net中实现同一用户名同时登陆,注销先前用户(转)
Web 项目中经常遇到的问题就是同一用户名多次登陆的问题,相应的解决办法也很多,总结起来不外乎这几种解决办法:将登陆后的用户名放到数据库表中:登陆后的用 户名放到Session中:登陆后的用户名放到A ...
- SQL cmd 实用工具学习 -1
启动 sqlcmd 实用工具并连接到 SQL Server 的默认实例 在"开始"菜单上,单击"运行". 在"打开"框中,键入 cmd,然后 ...
- Sys.WebForms.PageRequestManagerParserErrorException:无法分析从服务器收到的消息
我引起此原因的功能如下: 在aspx页面添加按钮 JS方法: function downPPT() { $("#Btn_DownPPT").click(); } <bo ...
- JAVA 对象引用,以及对象赋值(转)
原文链接:http://zwmf.iteye.com/blog/1738574 关键字: java对象 引用 Java对象及其引用 关于对象与引用之间的一些基本概念. 初学Java时,在很长一段时间里 ...
- 判别linux机器字节序为大端还是小端
代码如下: #include <iostream> #include <arpa/inet.h> #include <cstdio> using namespace ...
- 洛谷 P3383 【模板】线性筛素数
P3383 [模板]线性筛素数 题目描述 如题,给定一个范围N,你需要处理M个某数字是否为质数的询问(每个数字均在范围1-N内) 输入输出格式 输入格式: 第一行包含两个正整数N.M,分别表示查询的范 ...