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))))


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值