实现sqrt()开平方,要能处理精确的小数,类似leetcode 69,leetcode-cn没有找到浮点型的题。但是解法能通用。
直觉解释牛顿法:
利用导数和变化趋势,寻找方程y=f(x)的根。
虽然示意图烂大街了,还是坚持自己画一个:
黑色曲线为要拟合方程y=f(x)
红色直线为切线y-y0=y0'*(x-x0)
绿色直线为前一步找到的切线根再做垂线回到曲线一点(x,f(x))
迭代过程:从点A画切线,找到切线根B(xb,0),然后找到点C(xb,f(xb)),再从C做切线,找到切线根D,如此往复。
如图:E低于X轴,没关系,找的是根,向上找到G,假设这个时候G和垂线距离极小(偷懒),迭代结束,找到目标f(x)=0的x。
牛顿法迭代公式:
充分条件
既然方法简单粗暴,就肯定有缺点,不是对任何函数和任何起点都有效,这里不举复杂例子,以一个变种的y=x^2+b举例,牛顿法会导致反复横跳,不能收敛。
这里不需要知道牛顿法的全部支持和不支持的起点和曲线,只需要知道是否能支持sqrt曲线
sqrt:y=x^(1/2)大概是这样
sqrt-N大概这样
貌似可以收敛,但是我发现了一点问题:牛顿法的目标到底是谁?我要实现sqrt(),可是我并不是要用sqrt(),sqrt()不是已知功能,是要实现的功能。
基于sqrt()大概能实现什么功能,如果sqrt(x)=N,通过不断迭代找到的x(下标n+1)其实是N^2。给定一个N,得到了一个N^2,显然,这不是sqrt(),是pow2()
所以,要找到sqrt(N),公式应该是
f(x)=x*x-N,求f(x)的根,迭代出来满足这个条件的x(下标n+1),其实就是N的开方。
f(x)有两个根,这里我们只要找到右边的根就可以。sqrt输入输出只能是非负数。
根据“牛顿法迭代过程是做切线找根再做切线找根的过程”这一事实,x(下标n+1)的通用方程是
放到sqrt()实现的例子中
迭代过程伪代码
while(循环不终止)
x=0.5*(x+N/x)
浮点数 终止条件,精度随意
(abs(x*x-N)-0<1e-6)
结合起来
double sqrt(double N){
double x=N;
while(!(abs(x*x-N)-0<1e-6))
x=0.5*(x+N/x);
return x;
}
(出于习惯用N表示常量,而不是用x表示输入,因为在优化过程中N是常量)
代码更新:
int sqrt(int x)
{
// 避免除零错误,单独处理 x = 0 的情况
if (x == 0)
return x;
int t = x / 2 + 1;
while (t > x / t)
t = (t + x / t) / 2;
return t;
}
https://github.com/huqinwei/leetcode_practice/blob/main/leetcode_practice/Solution_0xx.h
扩展:可以出现更多类似此题,但是一定要确认是否满足充分条件