具体的任务是,对于在
初始时我们从给定的
假设我们目前的近似解是
整理后得到如下递推式:
直观地说,如果
牛顿迭代法的收敛率是平方级别的,这意味着每次迭代后近似解的精确数位会翻倍。 关于牛顿迭代法的收敛性证明可参考 citizendium - Newton method Convergence analysis
当然牛顿迭代法也同样存在着缺陷,详情参考 Xiaolin Wu - Roots of Equations 第 18 - 20 页分析
我们尝试用牛顿迭代法求解平方根。设
在实现的时候注意设置合适的精度。代码如下
// C++ Version
double sqrt_newton(double n) {
const double eps = 1E-15;
double x = 1;
while (true) {
double nx = (x + n / x) / 2;
if (abs(x - nx) < eps) break;
x = nx;
}
return x;
}
# Python Version
def sqrt_newton(n):
eps = 1e-15
x = 1
while True:
nx = (x + n / x) / 2
if abs(x - nx) < eps:
break
x = nx
return x
尽管我们可以调用 sqrt()
函数来获取平方根的值,但这里还是讲一下牛顿迭代法的变种算法,用于求不等式
// C++ Version
int isqrt_newton(int n) {
int x = 1;
bool decreased = false;
for (;;) {
int nx = (x + n / x) >> 1;
if (x == nx || (nx > x && decreased)) break;
decreased = nx < x;
x = nx;
}
return x;
}
# Python Version
def isqrt_newton(n):
x = 1
decreased = False
while True:
nx = (x + n // x) // 2
if x == nx or (nx > x and decreased):
break
decreased = nx < x
x = nx
return x
最后考虑高精度的牛顿迭代法。迭代的方法是不变的,但这次我们需要关注初始时近似解的设置,即
给出 Java 代码的实现:
public static BigInteger isqrtNewton(BigInteger n) {
BigInteger a = BigInteger.ONE.shiftLeft(n.bitLength() / 2);
boolean p_dec = false;
for (;;) {
BigInteger b = n.divide(a).add(a).shiftRight(1);
if (a.compareTo(b) == 0 || a.compareTo(b) < 0 && p_dec)
break;
p_dec = a.compareTo(b) > 0;
a = b;
}
return a;
}
实践效果:在
[!NOTE] Luogu [NOIP2001 提高组] 一元三次方程求解
题意: TODO
[!TIP] 思路
对函数求导并求
$f'(x) = 0$ 的点,即【极点】显然有两哥单峰极值
在极值划分的三个区间内牛顿迭代即可
详细代码
#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-4;
double a, b, c, d;
// f(x)
double f(double x) { return a * x * x * x + b * x * x + c * x + d; }
// f'(x)
double df(double x) { return 3 * a * x * x + 2 * b * x + c; }
// 牛顿迭代
double slove(double l, double r) {
double x, x0 = (l + r) / 2;
while (abs(x0 - x) > eps)
x = x0 - f(x0) / df(x0), swap(x0, x);
return x;
}
int main() {
cin >> a >> b >> c >> d;
double p = (-b - sqrt(b * b - 3 * a * c)) / (3 * a);
double q = (-b + sqrt(b * b - 3 * a * c)) / (3 * a);
printf("%.2lf %.2lf %.2lf\n", slove(-100, p), slove(p, q), slove(q, 100));
return 0;
}
[!NOTE] LeetCode 69. x 的平方根
题意: TODO
[!TIP] 思路
详细代码
// 牛顿迭代
class Solution {
public:
int mySqrt(int x) {
if (x == 0) return 0;
double C = x, x0 = x;
while (true) {
double xi = 0.5 * (x0 + C / x0);
if (fabs(x0 - xi) < 1e-7) {
break;
}
x0 = xi;
}
return int(x0);
}
};
// 二分略
"""
二分法:
当前数的平方都小于或者等于目标值时,就全部舍弃(因为我们要找的是第一个大于target的整数)
最后return的答案是,第一个大于target的整数减去1 即可。
"""
class Solution:
def mySqrt(self, x: int) -> int:
if x == 0 or x == 1:return x # 特殊case判断
l, r = 0, x
while l < r:
m = l + (r - l)//2
if m * m <= x:
l = m + 1
else:
r = m
return l - 1
[!NOTE] LeetCode 367. 有效的完全平方数
题意: TODO
[!TIP] 思路
详细代码
class Solution {
public:
bool isPerfectSquare(int num) {
if (num < 2) return true;
long long x = num / 2;
while (x * x > num) x = (x + num / x) / 2;
return x * x == num;
}
};
class Solution {
public:
bool isPerfectSquare(int num) {
int l = 1, r = num;
while (l < r) {
int mid = l + 1ll + r >> 1;
if (mid <= num / mid) l = mid;
else r = mid - 1;
}
return r * r == num;
}
};
# 也可以用普通的遍历一遍;这里用的是二分查找
class Solution:
def isPerfectSquare(self, num: int) -> bool:
l, r = 1, num
while l < r:
mid = (l + r) // 2
if mid * mid < num:
l = mid + 1
else:
r = mid
return l * l == num