全部学习汇总: https://github.com/GreyZhang/g_SICP
这里引出来的这个函数定义其实是容易理解的,average-damp实现的功能是输入一个函数之后构建出来一个新的函数。新的函数接受一个传入参数,然后返回传入参数以及原始函数计算值的平均值。接下来利用这个功能来重新构建前面的平方根求解函数。
首先,看一下之前的平方根求解实现。fixed-point的第一个参数是一个函数,函数的功能是求输入参数以及x/y的平均值。这样,如果结合前面的average-damp来实现这样的功能,只需要定一个实现x/y的函数,然后传给average-damp做参数即可。
(define (average-damp f)
(lambda (x) (average x (f x))))
(define (sqrt x)
(fixed-point (average-damp (lambda (y) (/ x y)))
1.0))
这样,在原来的基础上做修改实现平方根求解的方法如上。
这是几个测试的结果,看得出来计算的结果准确。
类似的,如果要实现一个立方根的求解,可以通过如下设计来实现。
(define (cube-root x)
(fixed-point (average-damp (lambda (y) (/ x (square y))))
1.0))
做一个测试:
这是几个测试的结果,计算的准确度还是可以的。
牛顿的求解方法:如果g(x)可导,那么g(x) = 0的解是f(x)的不动点。其中,f(x) = x - g(x)/g(x)求导。
那么,如何进行求导呢?这个,可以借助于求斜率的概念来实现。也就是上面的公式。
这样,求导的程序表达就可以这样实现,但是得选择一个足够小的dx。
这里,接着定义了一个比较小的dx。
这样,有了求导的表达之后可以进行牛顿方法的设计。首先实现牛顿表方法的相应的函数设计,接着利用不动点的求解函数来进行求解。这样,如果有一个满足牛顿方法求解条件的方程,就可以传入这个方程的表达函数以及一个初始的猜测值来进行相应方程的求解。
(define (deriv g)
(lambda (x) (/ (- (g (+ x dx)) (g x)) dx)))
(define dx 0.00001)
(define (cube x) (* x x x))
(define (newton-transform g)
(lambda (x) (- x (/ (g x) ((deriv g) x)))))
(define (newtons-method g guess)
(fixed-point (newton-transform g) guess))
(define (newton-sqrt x)
(newtons-method
(lambda (y) (- (square y) x)) 1.0))
(define (newton-cube-root x)
(newtons-method
(lambda (y) (- (cube y) x)) 1.0))
由此,可以重新设计平方根以及立方根的求解函数。
这是几个测试的结果。
针对上面的尝试设计,做一个复盘小结。
小结1:
前面看到的几种方法,最终全都统一到了求解不动点上。求解不动点,f(x) = x,通过不断地让guess <-- f(guess)最终可能达到收敛。但是,这样的表述不一定最终收敛,可能在某一个范围内跳动。
针对上面的问题,出来了改进的方案,guess <-- 1/2( guess + f(guess))。把这个作为一个调节器进行guess改进的设计,从而可以让求解收敛。
由此,求解x的平方根可以阐述为y * y = x,求解y的数值。其中,x不是未知数,y是未知数。
进一步,y = x / y。这样就可以利用不动点的求解方式来计算y的数值,当x的数值给定之后可以通过收敛尝试求得满足精度的y的数值,即为平方根。
以上,是最早的函数求解的方式由来。类似的,求解立方根可以变成求解 y = x / (y * y)的不动点。
小结2:牛顿方法
相比于小结1中分析出来的方案,牛顿方法有着更好的收敛速度。
一切,需要回归到这样的一个表述。如果g(x)可导,那么g(x) = 0的解是关于x的方程(上面的公式)的不动点。
为此,首先需要实现求导的表达,这里的理解可以借助于斜率,为此引入了dx的概念因子。有了这样的表达之后,可以完成牛顿方法的f(x)的转换。接下来,又是一个不动点的求解过程。
有了上面的小结,这部分原文的理解就特别容易了。
(define (newton-cubec a b c)
(newtons-method
(lambda (x) (+ (cube x) (* a (square x)) (* b x) c))
1.0))
以上是一个简单的实现。