兩種方法對浮點數求根號(二分法和牛頓法)
二分法和牛頓法求根號是面試中的經典題,如果沒提前接觸過,經典題將成為經典難題。我先上程式碼,後面再對程式碼進行解釋:
#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。