如何实现求平方根的过程
回想初中,数学老师教我们体育的时候,不,是体育老师教我们数学的时候,好吧,我忘了我的数学是谁教的,都还给老师了。老师告诉我们,求一个数的平方根,牛顿爵爷想出了很好的方法。通过一个初始猜测值,去验证它的平方是否接近于被开方数。如果没有达到理想中的接近状态,则基于这个初始值再做一次猜想。也就是获得这个值加上它除以被开方数所得的和的平均值。然后,同初始值一样进行验证,不断的进行猜想,直到达到理想状态,最后我们就获得了理想中的平方根。
回想过去,年少无知。只知道相信老师讲的,不爱仔细思索。现在,在学sicp(计算机程序的构造与解释,江湖疯传的魔法书,我也买来啃一啃)。在1.1.7里面讲到了如何用牛顿法求平方根,勾起了我对平方根求解的思考,故总结以下简单思考。
首先,猜测值为什么需要从1开始,然后需要以这个值与它除以被开方数的商的和的平均数为改进值。首先我们可以回想一下函数曲线,y = x²,它是开口向上,以y轴为对称轴的曲线。如果y确定,那么x值肯定落在x轴上值1和值y之间。因此,我们首先猜测x等于1.然后验证y除以1得到的商与1的大小比较。如果刚好相等,恭喜你,x就是1。这种美好情况只会出现于y为1的情况。如果不相等,那么我们就得用1和y/1的商的平均值a再去验证。直到我们猜想的这个值足够接近x.
假如y等于2分步描述如下:
猜测 | 商 | 平均数 |
---|---|---|
1 | 2/1=2 | (1 + 2)/ 2 = 1.5 |
1.5 | 2/1.5=1.3333 | (1.5 + 1.3333) / 2 = 1.4167 |
1.4167 | 2/1.4167=1.4118 | (1.4167 + 1.4118) / 2= 1.4142 |
1.4142...... |
function guss(num, y) {
return (y / num + num) / 2;
}
function isGoodEnough(num, y, differ) {
return Math.abs(Math.pow(num, 2) - y) <= differ;
}
function sqrt(y, differ) {
return (function sqrtIter(num, y) {
if (isGoodEnough(num, y, differ)) {
return num;
} else {
return sqrtIter(guss(num, y), y);
}
})(1, y)
}
这里涉及到为递归优化,后面我来修正一下,好冷啊,太穷,不敢开空调
继续昨天写的,观察sqrt函数,里面用了IIFE(立即执行函数表达式),这个sqrtIter函数语法形式上在尾部调用了自身,这就构成了递归调用。但从计算模式上来看,其实是迭代的。有关语法形式和计算模式的区别,我会专门写一篇文章。首先,直观上看,sqrtIter已经构成了尾递归。我们知道,递归会导致调用栈溢出,因为每一次函数调用,都会在已有栈上生成新的执行上下文。过多的执行上下文,一直得不到释放,会导致调用栈的大小超过Javascript执行引擎所能容纳的最大栈大小。当然了,现代Javascrip执行引擎对这种尾递归进行了很好的优化,基本原理就是,产生尾递归时,将前面无用的执行上下文出栈,然后尾调用形成的执行上下文入栈,这样尾递归就不会导致栈的大小一直增大。但是,如果我们面临在一个很老的Javascript执行引擎该怎么办了。其实所有的递归写法,都可以以迭代的写法写出来。只是有时候,递归的写法更直观一点。那么我们如何将上面的递归写法改为迭代写法了。
- 找到递归调用中值不断变化的变量
num
- 找到递归调用中值不断变化的变量的变化方式
guss(num, y)
- 找到递归调用中值不变的变量
y
,differ
- 找到递归调用弹出值的条件
isGoodEnough(num, y, differ)
- 将递归调用改为迭代语法
for
,while
,do while
都可以
示例如下
function sqrt(y, differ) {
for (var num = 1; !isGoodEnough(num, y, differ); num = guss(num, y)) {}
return num;
}
为了将我的思路表达清楚,所以我用了最常用的for循环。刚好契合for循环的初始语句,循环终止语句,迭代后执行语句。
写的有点乱,大家可以多多给我提意见。