二分法和牛顿法求根号是面试中的经典题,如果没提前接触过,经典题将成为经典难题。我先上代码,后面再对代码进行解释:
#include<iostream>
#include<string>
#define PRECISION 0.0002
using namespace std;
//二分法
//二分法通过缩小根值范围的方法来逼近结果
float sqrt1(float n) {
float min, max, mid; //min代表下边界,max代表上边界,mid为中间值也作为近似值
min = 0;
max = n;
mid = n / 2;
while (mid*mid>n + PRECISION || mid*mid<n - PRECISION)
{
mid = (max + min) / 2;
if (mid*mid < n + PRECISION) {
min = mid; //根值偏小,升高下边界
};
if (mid*mid > n - PRECISION) {
max = mid;//根值偏大,降低上边界
}
}
return mid;
}
//牛顿法
float sqrt2(float n) {
float k = n;
while (1) {
if (k*k > n - PRECISION && k*k < n + PRECISION) {
break;
}
k = 0.5*(k + n/k);//通过牛顿法得出
}
return k;
}
int main() {
float a = 11.283;
float res1, res2;
res1 = sqrt1(a);
res2 = sqrt2(a);
cout << "num is "<< a << endl;
cout << "二分法结果: " << res1 << endl;
cout <<"牛顿法结果: "<< res2 << endl;
cout << "二分法验证: " << res1 * res1 << endl;
cout <<"牛顿法验证: "<<res2 * res2 << endl;
system("pause");
return 0;
}
对于二分法,看注释就可以看得很明白了。对于牛顿法,有着更简洁的代码,但需要花一点数学思维来理解。其实,直接看牛顿法会对这道题的理解更费解,因为牛顿法的目标并不是为了开根号。 很多博主也不太负责任的直接贴上牛顿法的证明方法,对于读者理解这道题来说反而有误导作用。
牛顿法的目的是求方程的近似解,即函数曲线与横坐标的交点。比如,,求f(x)=0时,x的值。
那么这个牛顿法跟开根号有什么关系呢,可以转换一个思路, ==>
还有点难理解对吧,对于求开方,我们可以确定知道x的值,比如x=67,y=?。
那么上述公式就等价于, 求f(y)=0时,y的取值。这样一来就可以转换成牛顿法来解决。我们可以画出f(y)的图像,其实就是f(y)=y^2标准抛物线向下平移67个单位的样子:
上述图像就是x=67时的转换图像,我们要求图像和x轴正半轴的交点(根号值只可能为正),即上图标出的y点。
首先,在x轴右半轴上任意取一点p,p点作垂线求得与曲线的交点,即f(p),如上述黑线所示,p点未标出(就是y点左边那个交点)。
求出f(p)之后,再对点(p, f(p))作一条切线,这条切线务必与x轴有一个交点(这个交点比p更接近y)。
我们可以用同样的方法对这个交点操作一遍(如红线所示),那么新交点一定会更接近y。取最后一个epoch的取值当作y的近似值。
那么,就可以建立一种数学联系。
设第一个点为(p1,0),则其垂线与曲线交点为f(p1),则切点为(p1, f(p1)),切线斜率为f '(p1),知道斜率和一个确定点,就可以确定这条直线(切线),那么自然可以求得这条切线与x轴的交点(p2,0)。以此来确定,p2和p1之间的对应关系,这种对应关系可以泛化到p_{n+1}对pn的对应关系。
可以简单推一推:
上面得出比更接近y值的结论,所以我们只需把p的下标增大点就可以无限接近y了。
带入到原式子可以得到,k = 0.5*(k + n/k)
k代表的就是p,n代表的就是x。