如何实现求平方根的过程

回想初中,数学老师教我们体育的时候,不,是体育老师教我们数学的时候,好吧,我忘了我的数学是谁教的,都还给老师了。老师告诉我们,求一个数的平方根,牛顿爵爷想出了很好的方法。通过一个初始猜测值,去验证它的平方是否接近于被开方数。如果没有达到理想中的接近状态,则基于这个初始值再做一次猜想。也就是获得这个值加上它除以被开方数所得的和的平均值。然后,同初始值一样进行验证,不断的进行猜想,直到达到理想状态,最后我们就获得了理想中的平方根。

回想过去,年少无知。只知道相信老师讲的,不爱仔细思索。现在,在学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执行引擎该怎么办了。其实所有的递归写法,都可以以迭代的写法写出来。只是有时候,递归的写法更直观一点。那么我们如何将上面的递归写法改为迭代写法了。

  1. 找到递归调用中值不断变化的变量num
  2. 找到递归调用中值不断变化的变量的变化方式guss(num, y)
  3. 找到递归调用中值不变的变量y,differ
  4. 找到递归调用弹出值的条件isGoodEnough(num, y, differ)
  5. 将递归调用改为迭代语法for,while,do while都可以

示例如下

function sqrt(y, differ) {

    for  (var num = 1; !isGoodEnough(num, y, differ); num = guss(num, y)) {}

    return num;
}

为了将我的思路表达清楚,所以我用了最常用的for循环。刚好契合for循环的初始语句,循环终止语句,迭代后执行语句。
写的有点乱,大家可以多多给我提意见。

posted @ 2019-12-02 22:50  snicker  阅读(761)  评论(0编辑  收藏  举报