多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內最全IT社區平臺 聯系我們 | 收藏本站
阿里云優惠2
您當前位置:首頁 > 互聯網 > 【經典面試題】實現平方根函數sqrt

【經典面試題】實現平方根函數sqrt

來源:程序員人生   發布時間:2014-09-09 07:17:22 閱讀次數:3889次

本文將從一道經典的面試題說起:實現平方根函數,不得調用其他庫函數。

函數原型聲明如下:

double Sqrt(double A);

二分法

二分法的概念

,等價于求方程的非負根(解)。求解方程近似根的方法中,最直觀、最簡單的方法是二分法。“二分法”算法步驟如下:

  1. 先找出一個區間 [a, b],使得f(a)與f(b)異號。
  2. 求該區間的中點 m = (a+b)/2,并求出 f(m) 的值。
  3. 若 f(m) * f(a) < 0 則取 [a, m] 為新的區間, 否則取 [m, b].
  4. 重復第2和第3步至理想精確度為止。

二分法的過程可用下圖表示:



初始區間的選定

可見,若要用“二分法”求方程,首先要找到一個區間 [a, b],使得f(a),f(b)異號。a可以取0,這很容易想到;但b如何選取,即如何選取b=f(A),使得

取f(A)=A?不行,因為它不能始終保證



從圖像可知,當A>1時,才有。若用A作為第一步的上界,則在A小于1時,將無法得到正確結果。

同時可知,sqrt(A)在原點處的切線平行于y軸,所以找不到常數項為零的多項式f(A),使得

據此,可推出一個,使得成立。

,則t>=0,, 等價于,即

顯然,當時,上式成立,所以k可以取[1/4, +∞)的任意值,不妨取k=1/4,即有

,使得成立。

二分法的誤差

為了說明二分法的誤差,需要借助一個定理。
零點定理:若f(x)在(a,b)連續,且f(a)f(b)<0,則f(0)在(a, b)內有零點。
二分法每次迭代都能夠保證實際解在區間[a, b]范圍內,所以每次迭代的誤差都小于當前區間的寬度,這也是迭代的結束條件(通常誤差給定)。


二分法實現sqrt

根據以上分析,可以很快寫出sqrt的“二分法”版本:

double Sqrt(double A) { double a = 0.0, b = A + 0.25, m; // b = A 是錯誤的上界 // while(b - a > 2*DBL_EPSILON) { // sometimes dead cycle when m==a or m==b. for(;;) { m = (b + a)/2; if( m-a < DBL_EPSILON || b-m < DBL_EPSILON ) break; if( (m*m - A) * (a*a - A) < 0 ) b = m; else a = m; } return m; }

需要注意的是:

  • 初始上界是A+0.25,而不是A;
  • double型的精度DBL_EPSILON,不能隨意指定;


牛頓迭代法

下面介紹另一種應用廣泛的方法――牛頓迭代法。

牛頓法的概念

牛頓迭代法是迭代法的一種,它的迭代格式為:

 

牛頓法具有明顯的幾何意義,x[k+1]正是曲線在x[k]處的切線與x軸的交點的橫坐標。因此,牛頓法也稱切線法。

來自Wikipedia的一個動態圖很好的解釋了這種幾何意義:



牛頓法初值的選定

在開始牛頓迭代法之前,需要選定一個d迭代初值x0。根據前文分析,求sqrt(A),也可以取x0 = A+0.25;

牛頓法sqrt

根據牛頓法,求即求的非負根,
所以,此時的牛頓遞推式為:
 

離實現牛頓法還差關鍵一步:迭代的結束條件。

在不知道誤差公式,且要求誤差盡可能小情況下可以使用另一種方法――限定f(x)=0的誤差(此法僅限于不給誤差范圍,且要求誤差盡可能小時使用)。


據此,實現的sqrt如下:

double Sqrt(double A) { double x0 = A + 0.25, x1, xx = x0; for(;;) { x1 = (x0*x0 + A) / (2*x0); if(fabs(x1 - x0) <= DBL_EPSILON) break; if(xx == x1) break; // to break two value cycle. xx = x0; x0 = x1; } return x1; }

這段程序里的while條件是fabs(x1*x1-A) > 5*DBL_EPSILON是因為,x的誤差在2*DBL_EPSILON范圍內,所以x*x的誤差就在4*DBL_EPSILON范圍,考慮到浮點乘法的精度丟失,所以為5*DBL_EPSILON。

迭代法的理論基礎

迭代格式收斂的前提

迭代法在進行“迭代”之前,需將原方程改寫成的形式;再用迭代格式,逐次逼近的實際解x*。

整個過程的所有x構成了數列:(迭代序列),數列的遞推式即

所以,迭代法能夠求得近似解的前提是

         

其中x*為方程的實際解。


迭代格式收斂的條件

迭代法收斂的前提是 ,這很好理解;但要用此式驗證迭代式是否收斂,必須先通過遞推式求出通項

是否有更簡單的方法判定迭代格式收斂?當然有,下面介紹一個定理能夠簡便的判定迭代格式是否收斂,同時也能判定誤差。




定理  迭代格式收斂條件(Vipschitz條件)

若迭代函數滿足:

①一階導數連續;

②當x∈[a, b]時,有

③存在常數,使得

  1. 方程有唯一根x*;
  2. 對任意x0∈[a, b],迭代格式收斂,且
  3. ,(事后誤差估計);
  4. ,(事前誤差估計);



定理應用

下面以求的近似解為例,說明定理如何用:


1)判斷收斂

對應的迭代函數為:


它的導函數為:


在[0, A+0.25]區間內它顯然連續,且存在L=1/2使得;即滿足收斂的三個條件;


2)估計誤差(迭代次數)

有了L = 1/2;就可以根據定理估算:

①給定誤差情況下,需要迭代幾次?

②給定迭代次數,最終近似解的誤差?

比如,假設題目要求的精度是:小數點后2位(精確到0.01),那么誤差要小于0.01 / 2 = 0.005;只需根據初值x0和第一次迭代結果x1和定理的結論4,便可算出迭代次數k,這里不再羅列(公式編輯起來比較麻煩)。

同樣,根據x0,x1和結論4,也可以很方便的算出第k次迭代的誤差;


推廣――一般方程求近似解

本文指出的二分法、牛頓迭代法是求解方程近似解的常見方法,不僅僅限于求sqrt,但在本文所實現的sqrt程序的基礎上,可以很快實現求解其他方程的程序。

二分法

比如,如下程序段就是二分法求解任意方程f(x)=0的程序:

double bisection(double (*f)(double), double a, double b, double eps) { double m; assert( f != NULL && f(a) * f(b) < 0.0 && (b-a) > DBL_EPSILON); // (b-a) > DBL_EPSILON 即 b > a while( b - a > eps ) { m = (a + b)/2; if( f(m) * f(a) < 0.0 ) b = m; else a = m; } return m; }

這個程序較為“好用”,只需給出函數f,區間[a, b],誤差eps即可。


迭代法

同樣,有了對迭代法的理論基礎,我們知道了迭代法的誤差如何估計。下面是迭代法求一般方程的近似解的程序:

double iteration(double (*g)(double), double L, double x0, double eps) { double x1, t = L/(1 - L);; for(;;) { x1 = g(x0); if(fabs( t*(x1-x0) ) < eps) break; x0 = x1; } return x1; }

這個函數沒有上面的二分法那么好用,因為需要根據f(x)自行推出遞推函數g,并根據遞推函數的倒數找到一個常數L;再給出初值x0,誤差eps。


割線法

事實上,真正通用的牛頓法很難實現,因為從f(x)推出它的導函數f1(x)的過程并不容易。割線法可以避免這一難題,它使用差商:


來代替牛頓公式中的導數f'(xk),于是得到了“割線法”迭代公式:


割線法和牛頓法類似,有著明確的幾何意義。下面的gif動態地展示了割線法的幾何意義(若沒有看到動畫效果可嘗試刷新本頁):


(圖片來自Dr. Mathews的教案,)

割線法求一般方程的近似解的程序如下:

double secant(double (*f)(double), double x0, double x1, double eps) { double x2; for(;;) { x2 = x1 - f(x1)/(f(x1) - f(x0))*(x1 - x0); if( fabs(x2-x1) < eps ) break; x0 = x1; x1 = x2; } return x2; }

這個函數也很好使用,只需給出f,[a, b],eps即可。

割線法實現的sqrt如下:

double Sqrt(double A) { double x0 = 0, x1 = A+0.25, x2; double fx0 = x0*x0 - A, fx1 = x1*x1 - A; for(;;) { x2 = x1 - fx1*(x1-x0) / (fx1-fx0); if(fabs(x2 - x1) < 2*DBL_EPSILON) break; x0 = x1; fx0 = fx1; x1 = x2; fx1 = x2*x2 - A; } return x2; }


收斂速度對比

對于sqrt的實現,本文介紹了三種方法,分別為:二分法,牛頓法,割線法;

二分法的收斂速度

對于二分法,相鄰兩次的誤差ek+1和ek間的關系為:

由此,我們可以引申出迭代法收斂速度的判定標準:


定義  迭代法收斂的階

設序列{xk}收斂于x*,并記ek = xk - x*,如果存在非負常數c和正常數p,使得


則稱序列{xk}是p階收斂的。當p=1,且0<|c|<1時,稱為線性收斂;當p>1時,稱超線性收斂,特別是p=2時,稱平方收斂


由收斂階的定義可知,二分法是線性收斂的。

牛頓法的收斂速度

要確定牛頓法收斂的階,需要經過一番推導,限于篇幅,這里直接給出結論:

當x*是f(x)=0的單根(回顧一下二次方程)時,牛頓法至少是二階收斂的;

當x*是f(x)=0的重根時,牛頓法至少是一階收斂的。

割線法的收斂速度

分析割線法收斂的階同樣不易,它比牛頓法略慢一些;有知道的同學可以告訴我;

實驗對比

下面通過實驗對比幾種方法的收斂速度。實驗以不同版本的sqrt求出最終值所用的迭代次數為收斂速度的標準。
實驗程序如下:
#include <math.h> // for fabs sqrt #include <float.h> // for DBL_EPSILON DBL_DIG etc. #include <time.h> // for clock #include <stdio.h> #include <assert.h> int iterateCount = 0; double BisectionSqrt(double A) { double a = 0.0, b = A + 0.25, m; for(;;){ m = (b + a)/2; ++iterateCount; // count iterate. // printf(" %.15f ", m); if( m-a < DBL_EPSILON || b-m < DBL_EPSILON ) break; if( (m*m - A) * (a*a - A) < 0.0 ) b = m; else a = m; } return m; } double NewtonSqrt(double A) { double x0 = A + 0.25, x1, xx; for(;;) { x1 = (x0*x0 + A) / (2*x0); ++iterateCount; // count iterate. // printf(" %.15f ", m); if(fabs(x1 - x0) <= DBL_EPSILON) break; if(xx == x1) return x0; // break two value cycle. xx = x0; x0 = x1; } return x1; } double SecantSqrt(double A) { double x0 = 0, x1 = A+0.25, x2; double fx0 = x0*x0 - A, fx1 = x1*x1 - A; for(;;) { x2 = x1 - fx1*(x1-x0) / (fx1-fx0); ++iterateCount; // count iterate. // printf(" %.15f ", x2); if(fabs(x2 - x1) < 2*DBL_EPSILON) break; x0 = x1; fx0 = fx1; x1 = x2; fx1 = x2*x2 - A; } return x2; } int main() { #ifdef F_INFO puts("local machine floating point informations:"); printf("FLT_DIG: %d, %g ", FLT_DIG, FLT_EPSILON); printf("LDBL_DIG: %d, %g ", DBL_DIG, DBL_EPSILON); printf("LDBL_DIG: %d, %g ", LDBL_DIG, LDBL_EPSILON); #endif double x, res, stdres; while( scanf("%lf", &x) == 1 ) { stdres = sqrt(x); printf("%15g ", x); iterateCount = 0; res = BisectionSqrt(x); printf("%14g %3d ", res-stdres, iterateCount); iterateCount = 0; res = NewtonSqrt(x); printf("%14g %3d ", res-stdres, iterateCount); iterateCount = 0; res = SecantSqrt(x); printf("%14g %3d ", res-stdres, iterateCount); printf(" "); } return 0; }
程序輸出 是為了方便重定位到文本文件后,粘貼到excel等表格軟件中;格式串是為了方便控制臺查看;

測試數據由下面的python腳本生成:
for x in range(1, 10000): print x*0.01 for x in range(10000, 90000): print x for x in range(1000000, 9000000, 37): print x for x in range(100000000, 10000000000, 2311): print x

可以通過管道進行測試:
gcc sqrt_cmp.c -o sqrt_cmp # 編譯 python inputgen_sqrt_cmp.py | sqrt_cmp # 測試

經過如下數據試驗:
delta = 0.05 count = 1/delta for x in range(1, 2*int(count)): print x*delta
得到的迭代次數隨x的變化關系如下圖所示:

圖中,很坐標表示x值,縱坐標表示迭代次數。

試驗結果表明:牛頓法的收斂速度最快(迭代次數少),割線法次之,而二分法收斂最慢。

總結

本文分別描述了二分法、牛頓法、割線法,等幾種求方程近似解算法的步驟及實現;另外,從理論方面解釋了這些算法步驟背后數學原理;并將這些方法進行了一般化的推廣。最后,本文針對不同方法實現的sqrt進行了實驗對比。實驗結果表明:牛頓法的收斂速度快于割線法,割線法快于二分法。

生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關閉
程序員人生
主站蜘蛛池模板: 国内精品久久久久影院网站 | l欧美18一19sex性 | 精品国产一区二区三区19 | 日韩国产在线观看 | 2018av在线 | 国内精品久久久久影院亚洲 | 国产免费a v吧在线观看不卡 | 欧美午夜在线播放 | 无码精品一区二区三区免费视频 | 国产精品欧美一区二区三区不卡 | 日本草久 | 伊人色院成人蜜桃视频 | 精品视频在线观看 | xxxx日本高清 | 国产a久久精品一区二区三区 | 极品美女嫩模瑶瑶大尺度啪啪 | 欧美日韩在线精品一区二区三区 | 另类欧美日韩 | 国产另类图片 | 麻豆免费版在线观看 | 欧美日韩成人在线观看 | 久久国产成人精品国产成人亚洲 | 亚州视频一区 | 在线观看精品国产福利片100 | 亚洲欧美国产毛片在线 | 秋霞一级特黄真人毛片 | 9丨精品国产高清自在线看 ⅹxx日本护土 | 性v天堂| 蜜芽一区二区国产精品 | 久久亚洲伊人成综合人影院 | 无码精品一区二区三区免费视频 | 欧美xxxxhdvideos | 欧美一区二区另类有声小说 | 久爱免费观看在线网站 | 香港一级a毛片在线播放 | 亚洲欧美精品久久 | 欧美啪啪一级毛片 | 视频在线观看免费 | 永久在线观看视频 | 日韩精品欧美国产精品亚 | 日本大黄毛片视频 |