1. 程式人生 > >兩種方法對浮點數求根號(二分法和牛頓法)

兩種方法對浮點數求根號(二分法和牛頓法)

二分法和牛頓法求根號是面試中的經典題,如果沒提前接觸過,經典題將成為經典難題。我先上程式碼,後面再對程式碼進行解釋:

#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)=x^5+2x^2+8,求f(x)=0時,x的值。

那麼這個牛頓法跟開根號有什麼關係呢,f(x)=\sqrt x可以轉換一個思路,x = y^2 ==> y^2 - x = 0

還有點難理解對吧,對於求開方,我們可以確定知道x的值,比如x=67,y=?。

那麼上述公式就等價於f(y) = y^2 - 67, 求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的對應關係。

可以簡單推一推:

上面得出p_{n+1}p_n更接近y值的結論,所以我們只需把p的下標增大點就可以無限接近y了。

帶入到原式子可以得到,k = 0.5*(k + n/k)

k代表的就是p,n代表的就是x。