sqrt
来源:互联网 发布:qq安全中心mac版 编辑:程序博客网 时间:2024/06/05 03:42
delta = 0.00001
def sqrt(x):
def good_enough(guess, x):
return abs(guess * guess - x) < delta
while not good_enough(guess, x):
guess = 0.5 * (guess + x / guess)
return guess
已经很久没有读神马有营养的书了,尤其是越来越多项目排上倒海地涌过来。关键是每天都要跟需求从设计扯到技术,从技术扯到用户体验,从用户体验扯回营销,各种扯蛋。哥特么发现现在特别能扯了,按这样的升点方式以后要是做不了boss级人马真是对不起观众。过几天就清明了,酝酿拿两天年假结合节假日出外游玩一下,有木有人一起!!!有木有!!!
好,正文开始,上星期开始看SICP,果然很好看,决定写下读书笔记,其实说白了就是各种摘抄,当然哥会配合节奏自己瞎扯一下。示例程序哥全部用Python实现了一遍,因为蛋疼的Mit-Scheme需要学习Emacs,没耐心学了。。。
先用一个简单问题带起节奏:请用你熟悉的语言实现求平方根过程sqrt。
你可能会问,这有什么好实现的呢?
import math
print math.sqrt(100)
没错,当通用的算法都有现成的库对其封装,确实没有实现的意义。但是,我可以说,从现实意义上,这至少可以作为一道面试题(不是开玩笑,Google的面试会考你快排的实现,微软的面试会让你当面写出二分查找)。
求平方根,站在计算机的角度看,就是找到一个 X 使得 X * X=Y(实际上,应该是 Y - X * X 的绝对值少于某个足够少的阈值)。所以从某个猜测值 X0 开始,采用某种逼近策略,让 X0 逐渐靠近我们要求的 X,这就是求平方根的计算过程描述。而由于实数的稠密性,我们不可能采用诸如每次增加或减少0.0001的步进方法,而必须选取一种较好的策略。我们观察到,对于迭代过程中某次得到的 Xn,有 Y / Xn = Xn',换个思路,不就是每次让 Xn 和 Xn' 更加接近么?如何接近?可以考虑每次求出Xn与Xn'的平均值。所以对于每一次的 Xn,检测这个 Xn 是否“足够好”(可以有基于不同精度的准则),是则得出最终的X;否则求出 Xn+1 = (Xn + Xn')/ 2,继续检测上面条件。这也是牛顿法的一个特例:
guess = 1.0
delta = 0.00001
def sqrt(x):
def good_enough(guess, x):
return abs(guess * guess - x) < delta
while not good_enough(guess, x):
guess = 0.5 * (guess + x / guess)
return guess
事实上,由于阈值delta的精度问题,上面这种检测是否“足够好”的方法是比较粗糙的,它对于一个很小很小的数会失效:
0.5 * (1.0 + Min / 1.0) = 0.5 + Min / 2 ≈ 0.5
0.5 *(0.5 + Min / 0.5) = 0.25 + Min ≈ 0.25
0.5 * (0.25 + Min / 0.25) = 0.125 + Min * 2 ≈ 0.125
...
假如Min足够小,那么猜测值Xn会比Xn'(也就是Y / Xn)更快收敛到delta的平方根,从而使good_enough成立,但此时得出的 Xn 显然不是Min的平方根。一个更好的策略是检测猜测值从一次迭代到下一次的变化,当改变值相对于猜测值的比率很少时就结束。
对于一般的面试,到这里也就差不多了,但作为读书笔记,似乎有点短。下面先来看一下函数不动点的概念(有点穿越,无碍,先接受这种设定,一切都会变得可爱起来):
X 称为函数 F 的不动点,如果 X 满足方程 F(X) = X。对于某些函数,从某个猜测值开始反复地应用 F:
F(X),F(F(X)),F(F(F(X))),……
直到值的变化不大时,就可以找到它的一个不动点:
delta = 0.00001
def fixed_point(f, guess):
while abs(f(guess) - guess) > delta:
guess = f(guess)
return f(guess)
我们发现,求不动点跟刚才求平方根的思想有着惊人的相似,是否可以将平方根的方法更加general,更加形式化地抽象为求不动点的过程呢?由 Y = X * X 变换一下得到 X = Y / X,实际上就是求F(X) = Y / X 关于 X 的不动点:
def sqrt(y):
return fixed_point(lambda x:y / x, 1.0)
请注意,若直接按照这种变换进行迭代,搜寻并不收敛。考虑初始值X0,下一个猜测是X1 = Y / X0,再下一个猜测将是 X2 = Y / X1 = Y / (Y / X0) = X0,无限循环了。所以,我们必须引入一种技术减少相邻两个猜测值震动的幅度,让其尽快收敛。这里,我们考虑下一个猜测值是(1/2)(X + Y / X),而不是 Y / X:
def sqrt(y):
return fixed_point(lambda x:0.5 * (x + y / x), 1.0)
这种控制震荡,帮助收敛的方法我们称为平均阻尼技术。而且有没有发现,通过不动点结合平均阻尼的这种方法展开得到的求近似值序列,正好跟刚才介绍的牛顿法特例的序列一样?为了接下来的扩展,我们将平均阻尼的思想抽象出一个过程,作为返回值:
def average_damp(f):
return lambda x:0.5 * (x + f(x))
相应地,求平方根的函数可以改写为:
def sqrt(y):
return fixed_point(average_damp(lambda x:y / x), 1.0)
接下来,我们考虑牛顿法的一般形式(为的也是求平方根):如果G(X)是一个可微函数,那么方程G(X) = 0的一个解就是函数 F(X) = X 的一个不动点,其中:
F(X) = X - G(X) / D(G(X)) // D(G(X))是函数G在X点的导数
导数的定义我们直接用程序给出:
def deriv(g):
dx = 0.00001
return lambda x:(g(x + dx) - g(x)) / dx
有了deriv,牛顿法也可以方便地用程序表达出来了:
def newton_transform(g):
return lambda x:x - g(x) / deriv(g)(x)
def newton_method(g, guess):
return fixed_point(newton_transform(g), guess)
令G(X) = X * X - Y,通过牛顿法去找G(X) = 0的点,就可以确定 Y 的平方根了:
def sqrt(y):
return newton_method(lambda x: x * x - y, 1.0)
上面的两种求解平方根的方法,一个是求不动点,一个是使用牛顿法(实际上其表述也是一个不动点的计算过程),它们都将求平方根表述为更general的过程(相比于问题一开始给出的特例解答),通过对两个过程中间的一些构件比较,我们发现了以下特征:
求不动点 牛顿法
过程参数g lambda x:y/x lambda x:x*x - y
变换g的过程 average_damp newton_transform
初始猜想guess 1.0 1.0
作为普通程序员,数学公式可能会忘了,但是对于如何识别出程序里的基本抽象,基于它们进行进一步的构造,以创建威力更大的抽象,这样一种程序设计的思想应该保持高度敏感。这也正是后半部分的戏玉所在。在这两种方法上,我们实际上可以抽象出一个更general的过程,这个高阶过程可以灵活自如地操纵这些一般性的方法构件:
def fixed_point_of_transform(g, transform, guess):
return fixed_point(transform(g), guess)
对于求不动点方法,我们重塑为:
def sqrt(y):
return fixed_point_of_transform(lambda x:y / x, average_damp, 1.0)
而牛顿法,则可以重塑为:
def sqrt(y):
return fixed_point_of_transform(lambda x:x * x - y, newton_transform, 1.0)
所以到最后,你会发现这里重点分享的不是什么数学理论,而是要唤醒我们对过程抽象的触觉。作为实际应用的程序员,我们不是要对每个问题都进行尽可能抽象的描述,而是要根据工作中的实际情况,选择一个合适的抽象层次。这又回到了工程性思想的问题了,用我最喜欢的一个词,就是tradeoff(啊,想起李文军老师了,内牛满面)。
- sqrt
- sqrt
- sqrt
- sqrt
- sqrt
- sqrt
- sqrt
- sqrt.c
- sqrt源代码
- sqrt () 函数
- sqrt算法
- sqrt 函数
- Sqrt(x)
- Sqrt(x)
- Leetcode: sqrt
- Sqrt(x)
- I SQRT
- sqrt implementation
- 游标的使用
- 第一篇博客:只是随便写写
- 用javascript将中文名字拆分为姓与名的jquery插件
- ping 代码
- 快速查找内核源代码脚本
- sqrt
- 利用Base64在XML中存储BLOB
- LINUX系统分区及挂载点(转)讲的很清楚
- 编程之美学习笔记(二):中国象棋将帅问题
- cb6的DBNavigator删除提示 汉化
- SQL Server 开发指南
- XAML语法官方全面教程
- SQLserver--游标
- 离梦想差距还有多少