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的延续. 这里大师们想提醒我们思考的是"数"究竟是什么,在计算机系统里能够怎样实现"数".准备好 ...
随机推荐
- Qt 图形特效(Graphics Effect)介绍
原文链接:Qt 图形特效(Graphics Effect)介绍 QGraphicsEffect也是Qt-4.6引入的一个新功能.它让给图形元素QGraphicsItem增加更佳视觉效果的编程变得非常简 ...
- [转] unix/linux下线程私有数据实现原理及使用方法
在维护每个线程的私有数据的时候,我们可能会想到分配一个保存线程数据的数组,用线程的ID作为数组的索引来实现访问,但是有一个问题是系统生成的线程 ID不能保证是一个小而连续的整数,并且用数组实现的时候 ...
- CSS3 之 box-shadow
1. css3 box-shadow CSS3的box-shadow属性可以让我们轻松实现图层阴影效果 box-shadow: inset(可选 默认没有) x-offset y-offset ...
- 监控工具zabbix
1 安装zabbixyum install -y epel-release安装rpm包的lamp环境 yum install httpd mysql mysql-libs php php-mysql ...
- kafka的一些常用命令
启动zookeeper bin/zookeeper-server-start.sh config/zookeeper.properties & 启动kafka bin/kafka-server ...
- 使用hibernate优化
a. 在查询字符串中,应该总是使用jdbc的占位符?,或使用使用命名参数:,不要自查询中使用字符串值来代替非常量值. b. Flush会影响性能,频繁刷新影响性能,尽量减少不必要的刷新. c ...
- $.fn、$.fn.extend和$.extend的区别
$.fn $.fn是指jquery的命名空间,加在fn上的方法及属性,会对jquery实例每一个有效. 如:扩展$.fn.abc(),即$.fn.abc()是对jquery扩展了一个abc方法,那么后 ...
- C#在使用串口读一长段数据时,前面加延时,等串口缓冲全部收到再去读
Thread.Sleep(3);//延时一会,等接受完成再去读
- Oracle11g R2学习系列 之七安全性
其实,对于目前我使用的Oracle的水平来看,还达不到使用安全管理的高度,只是作为一个学习来看一下. 关于Oracle的安全管理,一般使用OEM来操作完成好了,入口是:OEM的“服务器”属性页中,选择 ...
- php随机获取金山词霸每日一句
header('Content-Type:text/html; charset=utf-8'); $nowyear=date("Y"); $nowmouth = date('m') ...