最近去读了SICP的第一章,初次领悟到了函数式编程的魅力。曾经尝试过好几次入坑,但是都是跟着网上奇奇怪怪的教程入的,不久就放弃了。这次要一鼓作气读完这本大部头!flag立起来了
对于求一些式子的值,比如x \sqrt{x} x ,曾经在课上学过牛顿法等去计算,但是一直又都没有真正理解为什么这样去计算,在SICP中,介绍了一种寻找不动点来计算这类值的方法。所谓不动点,就是当函数的输入等于输出时的一个点,这个时候有f ( x ) = x f(x) = x f ( x ) = x ,以刚才的y = x y = \sqrt{x} y = x 举例子,可以有以下推导:
y = x y 2 = x y = x y y = \sqrt{x}\\
y^2 = x \\
y = \frac{x}{y}
y = x y 2 = x y = y x
如果设f ( y ) = x y f(y) = \frac{x}{y} f ( y ) = y x ,就可发现,计算x \sqrt{x} x 的值就等价于寻找f ( y ) f(y) f ( y ) 这个函数的不动点,只要找到一个不动点,就可以确定这个开2次方的结果。
使用普通的迭代算法,即首先随意选择一个猜测值,然后带入函数中,如果不是不动点就将函数的输出作为输入再代入一次,直到找到一个不动点。
但是新的问题又来了,怎么保证这样做下去一定会找到一个不动点呢?关于这一点,SICP没有告诉我们,因为涉及到了大量的数学知识。但是我发现了这个定理
设y 0 y_0 y 0 为f f f 的一个不定点,当且仅当∣ f ′ ( y 0 ) ∣ < 1 |f'(y_0)| < 1 ∣ f ′ ( y 0 ) ∣ < 1 时,在y 0 y_{0} y 0 附近不动点有局部收敛性,也就是说,要保证这个方法收敛,就必须保证∣ f ′ ( y 0 ) ∣ < 1 |f'(y_0)| < 1 ∣ f ′ ( y 0 ) ∣ < 1 。如果将之前的函数求导,会发现是等于1的,并不小于1,这种方法不能收敛,SICP 给的方法是对原方程进行一些变形,每次得到的不是f ( y ) f(y) f ( y ) ,而是f ( y ) f(y) f ( y ) 和y y y 的平均值,这种方法叫做平均阻尼,也就是变成下面这种样子:
y = 1 2 ( x y + y ) y = \frac{1}{2}(\frac{x}{y} + y)
y = 2 1 ( y x + y )
这样的话得到了一个不一样的函数,计算出导数就是小于1的了。
SICP 在章节的最后留了一个问题,这样的变形对于求2次方是可以的,但是求4次方时就无能为力了,但是可以做两次平均阻尼来完成,也就是变成:
y = 1 2 ( 1 2 ( x y + y ) + y ) y = \frac{1}{2}(\frac{1}{2}(\frac{x}{y} + y) + y)
y = 2 1 ( 2 1 ( y x + y ) + y )
可以证明这个方程仍然和原方程相等,但是是在求n次方根的时候,要做多少次平均阻尼才能收敛呢,虽然 SICP 让做实验得出结论,但是有了上面的定理,我尝试使用公式推导出来了这个结果,以下是推导过程:
设求的是n次方根,需要做a次平均阻尼才能让这个函数收敛,不妨先观察 a=1,2,3 时候这个公式的变形情况
a = 1时
y = 1 2 ( x y n − 1 + y ) y = x 2 y n − 1 + y 2 y = \frac{1}{2}(\frac{x}{y^{n-1}}+y)\\\\
y = \frac{x}{2y^{n-1}}+\frac{y}{2}
y = 2 1 ( y n − 1 x + y ) y = 2 y n − 1 x + 2 y
a = 2时
y = 1 2 ( ( x 2 y n − 1 + y 2 ) + y ) y = x 2 2 y n − 1 + y ( 1 + 2 ) 2 2 y = \frac{1}{2}((\frac{x}{2y^{n-1}} + \frac{y}{2}) + y)\\\\
y = \frac{x}{2^2y^{n-1}} + \frac{y(1+2)}{2^2}
y = 2 1 ( ( 2 y n − 1 x + 2 y ) + y ) y = 2 2 y n − 1 x + 2 2 y ( 1 + 2 )
a = 3时
y = 1 2 ( ( x 2 2 y n − 1 + y ( 1 + 2 ) 2 2 ) + y ) y = x 2 3 y n − 1 + y ( 1 + 2 + 2 2 ) 2 3 y = \frac{1}{2}((\frac{x}{2^2y^{n-1}} + \frac{y(1+2)}{2^2}) + y)\\\\
y = \frac{x}{2^3y^{n-1}} + \frac{y(1+2+2^2)}{2^3}
y = 2 1 ( ( 2 2 y n − 1 x + 2 2 y ( 1 + 2 ) ) + y ) y = 2 3 y n − 1 x + 2 3 y ( 1 + 2 + 2 2 )
通过对上面几种情况的归纳,可以得出一般的规律:
y = x 2 a y n − 1 + y ( 1 + 2 1 + 2 2 + . . . + 2 a − 1 ) 2 a y = x 2 a y n − 1 + y ( 2 a − 1 ) 2 a y = \frac{x}{2^ay^{n-1}} + \frac{y(1+2^1+2^2+...+2^{a-1})}{2^a}\\\\
y = \frac{x}{2^ay^{n-1}} + \frac{y(2^a-1)}{2^a}
y = 2 a y n − 1 x + 2 a y ( 1 + 2 1 + 2 2 + . . . + 2 a − 1 ) y = 2 a y n − 1 x + 2 a y ( 2 a − 1 )
这个时候即可设
f ( y ) = x 2 a y n − 1 + y ( 2 a − 1 ) 2 a f(y) = \frac{x}{2^ay^{n-1}} + \frac{y(2^a-1)}{2^a}
f ( y ) = 2 a y n − 1 x + 2 a y ( 2 a − 1 )
对这个函数求导,可以得到
d f d y = ( 1 − n ) x 2 a y n + 2 a − 1 2 a \frac{df}{dy} = (1-n)\frac{x}{2^ay^n} + \frac{2^a-1}{2^a}
d y d f = ( 1 − n ) 2 a y n x + 2 a 2 a − 1
可以知道,在f ( y ) f(y) f ( y ) 的不动点处,y n = x y^n = x y n = x ,将这个结论带入,并且已知导数的绝对值小于1,可以得到
∣ 1 − n 2 a + 2 a − 1 2 a ∣ < 1 ∣ 2 a − n 2 a ∣ < 1 ∣ 1 − n 2 a ∣ < 1 ∣ n 2 a − 1 ∣ < 1 − 1 < n 2 a − 1 < 1 0 < n 2 a < 2 |\frac{1-n}{2^a} +\frac{2^a-1}{2^a}| < 1\\\\
|\frac{2^a-n}{2^a}| < 1\\\\
|1-\frac{n}{2^a}| < 1\\\\
|\frac{n}{2^a}-1| <1\\\\
-1 < \frac{n}{2^a}-1 < 1\\\\
0 < \frac{n}{2^a} < 2
∣ 2 a 1 − n + 2 a 2 a − 1 ∣ < 1 ∣ 2 a 2 a − n ∣ < 1 ∣ 1 − 2 a n ∣ < 1 ∣ 2 a n − 1 ∣ < 1 − 1 < 2 a n − 1 < 1 0 < 2 a n < 2
由于我们知道n一定大于0,所以可以省略大于0的条件,得到下面的式子
n 2 a < 2 n < 2 a + 1 a + 1 > log 2 n a > log 2 n − 1 \frac{n}{2^a} < 2\\\\
n < 2^{a+1}\\\\
a+1 > \log_2n\\\\
a > \log_2n-1
2 a n < 2 n < 2 a + 1 a + 1 > log 2 n a > log 2 n − 1
真相大白了,原来做平均阻尼的次数只需要log 2 n \log_2n log 2 n 次就够了,最终的练习是使用Scheme编写一个能够计算n次方根的过程,下面是代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 (define (repeated f n) (if (= n 1 ) f (lambda (x) (f ((repeated f (- n 1 )) x))))) (define tolerance 0.00001 ) (define (fixed-point f first-guess) (define (close-enough? v1 v2) (< (abs (- v1 v2)) tolerance)) (define (try guess) (let ((next (f guess))) (if (close-enough? guess next) next (try next)))) (try first-guess)) (define (average a b) (/ (+ a b) 2.0 )) (define (average-damp f) (lambda (x) (average (f x) x))) (define (ilog2 x) (define (iter it res) (if (= it 1 ) res (iter (arithmetic-shift it -1 ) (+ res 1 )))) (iter x 0 )) (define (pow a b) (cond ((= a 0 ) 0.0 ) ((= b 0 ) 1.0 ) (else (let ((res (pow a (arithmetic-shift b -1 )))) (* res res (if (= (remainder b 2 ) 0.0 ) 1.0 a)))))) (define (sqrtn x n) (fixed-point ((repeated average-damp (ilog2 n)) (lambda (y) (/ x (pow y (- n 1 ))))) (average x (pow x n)))) (sqrtn 2 12 )
我在最后最终做了一个2 12 \sqrt[12]{2} 1 2 2 的计算,结果是1.059465254018959,代入计算器中基本符合计算要求,可以观察得到之前的推演是正确的
总结:
之前对偏数学上理解程序很弱,需要进一步提高