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) 解题总结的更多相关文章

  1. SICP 习题 (1.13) 解题总结

    SICP习题1.13要求证明Fib(n)是最接近φn/√5 的整数,其中φ=(1+√5)/2 .题目还有一个提示,提示解题者利用归纳法和斐波那契数的定义证明Fib(n)=(φn - ψn) / √5 ...

  2. SICP 习题 (1.7) 解题总结

    SICP 习题 1.7 是对正文1.1.7节中的牛顿法求平方根的改进,改进部分是good-enough?过程. 原来的good-enough?是判断x和guess平方的差值是否小于0.001,这个过程 ...

  3. SICP 习题 (1.14)解题总结

    SICP 习题 1.14要求计算出过程count-change的增长阶.count-change是书中1.2.2节讲解的用于计算零钱找换方案的过程. 要解答习题1.14,首先你需要理解count-ch ...

  4. SICP 习题 (1.8) 解题总结

    SICP 习题1.8需要我们做的是按照牛顿法求平方根的方法做一个求立方根的过程. 所以说书中讲牛顿法求平方根的内容还是要好好理解,不然后面这几道题做起来就比较困难. 反过来,如果理解了牛顿法求平方根的 ...

  5. SICP 习题 (1.10)解题总结

    SICP 习题 1.10 讲的是一个叫“Akermann函数”的东西,去百度查可以查到对应的中文翻译,叫“阿克曼函数”. 就像前面的解题总结中提到的,我是一个数学恐惧者,看着稍微复杂一点的什么函数我就 ...

  6. SICP 习题 (1.41)解题总结

    SICP 习题1.41 看似和周边的题目没有关系,突然叫我们去定义一个叫double的过程,事实上这道题的核心还是高阶函数. 题目要求我们定义一个过程double,它以一个过程作为參数,这个作为參数的 ...

  7. SICP 习题 (2.10)解题总结: 区间除法中除于零的问题

    SICP 习题 2.10 要求我们处理区间除法运算中除于零的问题. 题中讲到一个专业程序猿Ben Bitdiddle看了Alyssa的工作后提出了除于零的问题,大家留意一下这个叫Ben的人,后面会不断 ...

  8. SICP 习题 (2.7) 解题总结 : 定义区间数据结构

    SICP 习题 2.7 開始属于扩展练习,能够考虑不做,对后面的学习没什么影响.只是,假设上面的使用过程表示序对,还有丘奇计数你都能够理解的话,完毕这些扩展练习事实上没什么问题. 习题2.7是要求我们 ...

  9. SICP 习题 (2.6) 解题总结:丘奇计数

    SICP 习题 2.6 讲的是丘奇计数,是习题2.4 和 2.5的延续. 这里大师们想提醒我们思考的是"数"究竟是什么,在计算机系统里能够怎样实现"数".准备好 ...

随机推荐

  1. Qt 图形特效(Graphics Effect)介绍

    原文链接:Qt 图形特效(Graphics Effect)介绍 QGraphicsEffect也是Qt-4.6引入的一个新功能.它让给图形元素QGraphicsItem增加更佳视觉效果的编程变得非常简 ...

  2. [转] unix/linux下线程私有数据实现原理及使用方法

     在维护每个线程的私有数据的时候,我们可能会想到分配一个保存线程数据的数组,用线程的ID作为数组的索引来实现访问,但是有一个问题是系统生成的线程 ID不能保证是一个小而连续的整数,并且用数组实现的时候 ...

  3. CSS3 之 box-shadow

    1. css3 box-shadow CSS3的box-shadow属性可以让我们轻松实现图层阴影效果 box-shadow:  inset(可选 默认没有) x-offset    y-offset ...

  4. 监控工具zabbix

    1 安装zabbixyum install -y epel-release安装rpm包的lamp环境 yum install  httpd mysql mysql-libs php php-mysql ...

  5. kafka的一些常用命令

    启动zookeeper bin/zookeeper-server-start.sh config/zookeeper.properties & 启动kafka bin/kafka-server ...

  6. 使用hibernate优化

    a. 在查询字符串中,应该总是使用jdbc的占位符?,或使用使用命名参数:,不要自查询中使用字符串值来代替非常量值.   b.  Flush会影响性能,频繁刷新影响性能,尽量减少不必要的刷新.   c ...

  7. $.fn、$.fn.extend和$.extend的区别

    $.fn $.fn是指jquery的命名空间,加在fn上的方法及属性,会对jquery实例每一个有效. 如:扩展$.fn.abc(),即$.fn.abc()是对jquery扩展了一个abc方法,那么后 ...

  8. C#在使用串口读一长段数据时,前面加延时,等串口缓冲全部收到再去读

    Thread.Sleep(3);//延时一会,等接受完成再去读

  9. Oracle11g R2学习系列 之七安全性

    其实,对于目前我使用的Oracle的水平来看,还达不到使用安全管理的高度,只是作为一个学习来看一下. 关于Oracle的安全管理,一般使用OEM来操作完成好了,入口是:OEM的“服务器”属性页中,选择 ...

  10. php随机获取金山词霸每日一句

    header('Content-Type:text/html; charset=utf-8'); $nowyear=date("Y"); $nowmouth = date('m') ...