這段日子,空閒的時候在看SICP(Structure and Interpretation of Computer Programs),發現滿有意思的。
剛開始就看到了一個求平方根的算法。滿腦子搜尋,好像從小就沒學過如何計算平方根,只知道平方根的定義。於是就打算記錄下來。
用牛頓法求解平方根:
輸入:x
輸出:y
要求:(y * y - x) < 0.001 (注:因爲平方根運算一般都牽涉到一個精度的問題)
算法:
1. 猜測一個值作爲y
2. 如果y的精度不夠,則用如下公式改進y:
y = (y + x/y) / 2;
然後重複第二步;
如果y的精度夠了,就輸出y;
不知道上面的表達是不是夠清楚了。下面給出我的程序吧。
#include <iostream>
using namespace std;
double my_abs(double y)
{
if(y < 0)
return (-y);
else
return y;
}
bool good_enough(double guess, double x)
{
return ( my_abs( (guess * guess) - x) < 0.001);
}
double try_sqrt(double guess, double x)
{
if(good_enough(guess, x))
return guess;
else
return try_sqrt( (guess + (x / guess)) / 2.0, x);
}
double my_sqrt(double x)
{
return try_sqrt(1.0, x);
}
int main()
{
cout << my_sqrt(2) << endl;
return 0;
}
當然,可以看到,這裏對於最初的那個猜測的值,取的是1.0,關於這個初始值的取法,不知道有沒有好的方法。另外還有一個問題就是關於如何判斷是否夠精確了。這裏的算法只是判斷該值的平方與那個x的差值是否小於0.001。還有一種判斷方法就是比較上一次的結果和這次的結果的差值,這個方法與上面的那個方法的差別我還沒考慮過。
最後呢,還有一個就是與標準庫的sqrt()函數的比較問題。
上面那個程序的運行結果如下:
1.41422
而標準庫的結果是:
1.41421
不知道標準庫裏是用什麼算法計算的。
另外,如果把那個判斷精度的0.001改成0.0001,好像結果沒有變化。但如果改成0.01的話,就明顯看出結果的誤差,這個算法的結果是:1.41667。另外,如果算36的平方根的時候,取0.01作爲精度的話,結果就有問題了,這個算法的結果是:6.00025,如果取0.001作爲精度,那結果就正確了,嘻嘻~
等那天有空再去看看標準庫的算法吧,懷疑可能就是判斷精度的問題上有區別。呵呵。
哦,還有一個需要記錄的,就是看SICP上的說法,求平方根只是牛頓法的一個特例,它還有求立方根的算法,其普遍形式好像是一個求解方程的算法,等我看了再作記錄吧。
補充:
沒解決問題的情況下,總覺得心有不甘,於是我就又試了一下,這次取相鄰兩次計算值的差來作爲精度的確定,發現結果就和上面的不一樣了,還是先貼程序:
bool good_enough2(double guess1, double guess2)
{
if( my_abs( guess1 - guess2) < 0.01)
return true;
else
return false;
}
double try_sqrt2(double guess, double x)
{
double next_guess = (guess + (x / guess)) / 2.0;
if(good_enough2(next_guess, guess))
return next_guess;
else
return try_sqrt2( next_guess, x);
}
double my_sqrt2(double x)
{
return try_sqrt2(1.0, x);
}
注意到上面我用紅色標註的那個0.01了吧?在前一種算法中,需要0.001才能達到正確精度,而這個算法只需要0.01就可以達到精度了。當然,我這個精度比較是通過求36的平方根來算的。第二個算法裏用0.01就能得到正確的值,而用0.1的話,結果就和第一個算法用0.01的結果一樣了。
不過這裏面的緣由似乎也很簡單吧,因爲後面的算法是直接比較結果值,而前者是比較平方後的結果值,這樣誤差也是有類似於平方關係的吧。就比如現在用的0.01的精度,平方後的精度就是0.0001,比原來的0.001的精度還要高,那結果自然正確。而如果取0.1的話,那麼平方之後是0.01,自然就沒有第一種那麼精確了。
現在我通過測試驗證,第二種算法的和標準庫的結果是一致的,就比如前面測試的求2的平方根,第一種算法得出的結果是1.41422,而標準庫的結果都是1.41421,第二種算法當那個精度值取0.01的時候,結果同第一種算法,而取0.001的時候,結果就與標準庫的相同了。
其實想一下也很簡單,第二種算法比較的是結果的值,其精度是直接取決於那個精度值的,因此精度可以較高。而第一種算法的那個精度值是結果平方後的,也就是需要精度更爲高的值(也就是1之前的0應該更多,呵呵),才能達到高精度。實驗證明,第一種算法取0.00001的時候,仍舊是1.41422,而取0.000001的時候,結果就是1.41422了。
好了,平方根算法也研究得差不多了,至少表面樣子上已經差不多了。如果從效率上來說,兩種算法似乎差不多吧。不過第一種算法“平方之後再比較”應該比第二種算法“直接比較結果”來得慢一些吧,畢竟多了一個乘法運算了。由於數學能力和算法分析能力已經不行了,就不計算複雜度了吧。
再補充一下:
剛纔這些結果都是用cout的標準輸出精度來看的,所以精確度也就這點了。我剛纔又找了一下標準庫,發現通過std::setprecision() 可以設置輸出的精度。結果就發現問題了,就是最後還是發現了結果不同。而且我那個0.001不管設得再小,也好像和標準庫的結果不一樣,雖然誤差很小很小,不過這又使得我懷疑標準庫的算法還是有所不同。下面是結果:
1.4142135623730949 算法一
1.4142135623730949 算法二
1.4142135623730951 標準庫的算法
上面這個結果是我已經加了好多0的情況下了(具體多少個我也沒數過,反正好多,呵呵)
就先這樣吧。哪天看了標準庫的算法,再做補充吧。