牛頓迭代法求解平方根


假設現在輸入一個整數,希望通過某種方式來求得該整數的平方根,要求得到盡可能大的精度。

和 LeetCode 上的原題 LeetCode 69 不同,這里要求得到盡可能大的精度,因此一般的二分法無法處理這個問題

處理思路

考慮定義一個函數 \(f(x) = x ^ 2 - a\),那么當 \(f(x)\)\(0\) 時,所對應的正 \(x\) 坐標就是 \(a\) 的平方根。現在,在 \(f(x)\) 上的任意一點,做出 \(f(x)\) 處對應的切線,此時的橫坐標為 \(x_i\),這條切線和 \(X\) 軸的交點的橫坐標為 \(x_{i + 1}\),具體如下圖所示:

function.png

由於在 \(f(x)\) 處的切線的斜率為當前位置的 \(f(x)\) 的倒數,因此有如下的關系:

\[f(x_i) / (x_i - x_{i + 1}) = f'(x_i) \]

將該關系進行轉換,可以得到 \(x_{i + 1}\)\(x_i\) 之間的對應關系:

\[x_{i + 1} = x_i - f(x_i) / f'(x_i) \]

由於 \(f(x) = x^2 - a\),由求導公式可得 \(f'(x) = 2x\),將其帶入上述的公式可得:

\[\begin{aligned} x_{i + 1} &= x_i - (x_i^2 - a) / 2x_i \\ &=x_i - x_i / 2 + a / 2x_i\\ &=(x_i + a/x_i) / 2 \end{aligned} \]

\(x_i\) 非常接近 \(\sqrt a\) 時,則有如下的對應關系:

\[x_{i + 1} = (\sqrt a + \sqrt a) / 2 = \sqrt a \]

即經過不斷地迭代,最終結果收斂於 \(\sqrt a\)

編碼實現

public static double sqrt(int n) {
    int ub = 20; //  20 次左右的迭代可以解決 32 位有符號整數的平方根
    double y = 0.5 * n; // 初始值默認為 0.5 倍的 n,如果能夠取得更好的初始值,算法性能會有進一步的提升
    double rootx = Math.sqrt(n); // 實際平方根,用於比較
    for (int i = 0; i < ub; ++i) {
        System.out.printf("%05d: %25.16f %25.16f\n", i, y, Math.abs(y - rootx) / rootx);
        double newy = 0.5*(y + (double) n / y); // 迭代
        if (newy == y) {
            System.out.println("Converged");
            break;
        }
        y = newy;
    }

    return y;
}

時間復雜度:可以看到,如果有一個合適的初始值,牛頓迭代法可以是一個常數時間內的操作,即 \(O(1)\)

空間復雜度:只需要少量的幾個中間變量,因此空間復雜度為 \(O(1)\)


參考:

[1] 《編程珠磯(續)》Jon Bentley 第 14 章 編寫數值計算程序


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM