1. 程式人生 > >牛頓迭代法 簡單入門

牛頓迭代法 簡單入門

引言

牛頓迭代法是在計算機程式設計領域廣泛用來求解方程的演算法。

此演算法類似於二分列舉(僅僅感覺上很類似= =)
我們每次列舉一個值X0X_0,代入方程看是否為根,不是的話則將X0X_0的值變為:
X0=X0F(X0)/F(X0)X_0 = X_0 - F(X_0)/F'(X_0) (其中F(x)F'(x)F(x)F(x) 的導數)

其證明過程借用了高數中的基本知識泰勒級數,此處不再贅述。

看起來很簡單對嗎?事實上就是這麼簡單。

應用一

下面比如我們要求出下面方程的一個根:
F(x)=5x3+4x2+2F(x) = 5x^3 + 4x^2 + 2

程式碼如下:

const double eps = 1e-6;
double F(double x){return 5*x*x*x + 4*x*x + 2;}
double F1(double x){return 15*x*x + 8*x;}       //F(x)的導函式
int newton(double x){                           //x是初始列舉值
    while(fabs(F(x)) > eps) x -= F(x)/F1(x);    //牛頓迭代
    return x;
}

如果想求出一個範圍內的所有的根,則可以在該範圍內列舉初始值x,來逼近根的值。

應用二

對於ACM 競賽,牛頓迭代最重要的應用還是在於其能用於求根號。

你可能會有疑惑,求根號不是有sqrt()sqrt() 函式嗎,為什麼要自己寫。
這是因為可能存在下面的兩種情形:

對於該問題我們可以使用牛頓迭代+高精度除法。
我們以開平方根舉例
對於一個已知的數 a,開根號本質上是求一個X0X_0 使得 X02=aX_0 ^2 = a
也就是求函式
F(x)=x2aF(x) = x^2 - a 的根
因為F(x)=2xF'(x) = 2x

故每次迭代,x的變化值為:
x=x(x2a)/(2x)=(x+a/x)/2

x = x - (x^2-a)/(2x) = (x+a/x)/2
所以再套一個高精度除法或者Java的BigInteger就好啦

//51Nod 1166
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main{
	public static void main(String[] args) throws Exception {
		BufferedReader reader = new BufferedReader(new InputStreamReader(
				System.in), 1 << 16);
		String nStr = reader.readLine();
		BigDecimal n = new BigDecimal(nStr);
		BigDecimal ans = new BigDecimal(nStr.substring(0, nStr.length() / 2));
		BigDecimal tmp = BigDecimal.ONE;
		BigDecimal two = new BigDecimal("2");
		int length = 2;
		while(true){
			tmp = ans.add(n.divide(ans, length, RoundingMode.HALF_DOWN)).divide(two, length, RoundingMode.HALF_DOWN);
			if(tmp.subtract(ans).abs().compareTo(BigDecimal.ONE) == -1)
				break;
			ans = tmp;
		}
		String str = ans.toString();
		System.out.println(str.substring(0, str.length() - length - 1));
	}
	
}

(二)底層優化
雖然sqrt()sqrt() 已經很方便,但確實是存在比其更強大的手寫演算法,這個黑科技來自遊戲雷神之錘3的原始碼:(細節見末尾推薦文章)

int sqrt(float x) { 
    if(x == 0) return 0; 
    float result = x; 
    float xhalf = 0.5f*result; 
    int i = *(int*)&result; 
    i = 0x5f375a86- (i>>1); // what the fuck? 
    result = *(float*)&i; 
    result = result*(1.5f-xhalf*result*result); // Newton step, repeating increases accuracy 
    result = result*(1.5f-xhalf*result*result); 
    return 1.0f/result; 
}

推薦文章:
馬同學的詳細講解
百度百科
黑科技來源