牛頓法求解一元函數
對於一個簡單的一元方程我們可以通過代數運算來求解,但是對於一個非線性的複雜一元函數例如 這樣的方程,想要通過人力計算就很難辦到。
下面介紹利用牛頓法來構建的一個一元函數方程求解的程序。
牛頓法
當方程沒有求根公式或者求根公式特別複雜時,利用牛頓法都可以進行迭代求解。
維基對牛頓法的定義爲:
它是一種在實數域和複數域上近似求解方程的方法。方法使用函數f(x)的泰勒級數的前面幾項來尋找方程 f(y)=0的根。
先理解下泰勒級數
如果一個函數是以實數或者複數爲變量,並且該函數是可導的,那麼這個函數就可以被展開爲泰勒多項式。
有如下定理:
設 n 是一個正整數。如果定義在一個包含 a 的區間上的函數 f 在 a 點處 n+1 次可導,那麼對於這個區間上的任意 x,都有:
其中的多項式稱爲函數在a 處的泰勒展開式,剩餘的 是泰勒公式的餘項,是 的高階無窮小。
牛頓法就是利用一階泰勒展開 ,對 求解得到 ,利用其對 進行不斷的迭代,直到 收斂到正確的解或者逼近正確的解。
迭代公式爲:
用一段僞代碼表示爲:
while x<0.000000001 //精度
x0 = x1
x1 = x0 - F(x0)/Fdao(x1)
以上爲求解方程的核心算法。
下面是對函數表達式進行處理,包括表達式的預處理、前綴表達式轉化後綴表達式、表達式計算和表達式的求導及計算。
表達式的預處理
對於用戶輸入的表達式,我們要轉化成一定的格式方便我們後續處理。
例如:
- 去除表達式中夾雜的空格
+2x-1
省略其前面的+
2x
轉化爲2*x
-x
轉化爲(0-x)
經過簡單的預處理得到一箇中綴表達式。
中綴表達式轉化爲後綴表達式
轉化過程我們可以利用棧來作爲臨時容器,用隊列輸出後綴表達式。具體算法使用迪傑斯特拉提出的調度場算法,具體算法步驟如下(摘自維基):
- 當還有記號可以讀取時:
- 讀取一個記號。
- 如果這個記號表示一個數字,那麼將其添加到輸出隊列中。
- 如果這個記號表示一個函數,那麼將其壓入棧當中。
- 如果這個記號表示一個函數參數的分隔符(例如,一個半角逗號 , ):
- 從棧當中不斷地彈出操作符並且放入輸出隊列中去,直到棧頂部的元素爲一個左括號爲止。如果一直沒有遇到左括號,那麼要麼是分隔符放錯了位置,要麼是括號不匹配。
- 如果這個記號表示一個操作符,記做o1,那麼:
- 只要存在另一個記爲o2的操作符位於棧的頂端,並且
- 如果o1是左結合性的並且它的運算符優先級要小於或者等於o2的優先級,或者
- 如果o1是右結合性的並且它的運算符優先級比o2的要低,那麼將o2從棧的頂端彈出並且放入輸出隊列中(循環直至以上條件不滿足爲止);
- 然後,將o1壓入棧的頂端。
- 如果這個記號是一個左括號,那麼就將其壓入棧當中。
- 如果這個記號是一個右括號,那麼:
- 從棧當中不斷地彈出操作符並且放入輸出隊列中,直到棧頂部的元素爲左括號爲止。
- 將左括號從棧的頂端彈出,但並不放入輸出隊列中去。
- 如果此時位於棧頂端的記號表示一個函數,那麼將其彈出並放入輸出隊列中去。
- 如果在找到一個左括號之前棧就已經彈出了所有元素,那麼就表示在表達式中存在不匹配的括號。
- 當再沒有記號可以讀取時:
- 如果此時在棧當中還有操作符:
- 如果此時位於棧頂端的操作符是一個括號,那麼就表示在表達式中存在不匹配的括號。
- 將操作符逐個彈出並放入輸出隊列中。
- 退出算法。
假如輸入的表達式爲(a+b) * (c * (d+e))
,經過轉化得到a b + c d e + * *
這樣的後綴表達式。
表達式的計算
首先要解決一個問題,通過哪種數據結構來存儲表達式,表達式的計算是一個不遞歸環的過程,我們可以用“棧+循環”來處理,但爲了方便遞歸處理和後序的遞歸求導,這裏用二叉樹來存儲後綴表達式。
下面是(a+b) * (c * (d+e))
的表達式樹:
該樹的特點是:葉子節點爲操作數,父節點爲操作符,可以從葉子節點開始一直向上遞歸最終求解出整個表達式的結果。
構建表達式樹
利用後綴表達式很容易的得出表達式樹,其構建的算法如下:
- 新建一個棧,該棧保存的是一個表達樹
- 從頭到尾遍歷後綴表達式,
- 當發現操作數時,我們將其存爲操作數節點,入棧;
- 當發現操作符時,我們將其存爲操作符節點,
- 若是二元操作符,我們將棧頂的兩個節點彈出,作爲該操作符的節點左右節點,然後將該子樹入棧
- 若是一元操作符,我們將棧頂節點彈出,作爲該操作符的右節點,然後將該子樹入棧
最終棧底只有一個表達式樹,其他情況均視爲表達式錯誤。
求值
以根結點爲操作符,左右子樹爲操作數,可以對二叉樹進行遞歸求解,最終得到一個含有未知數 的簡化表達式。
表達式樹的求導和計算
對於上面得到的二叉表達樹,由於二叉表達樹根結點總是操作符,左右子樹總是表達式,因此可以對二叉表達樹進行遞歸求導。求導的過程也是一個遞歸的構建導函數樹的過程。
例如:一個子樹是這樣的
設計一個導函數dao(+,a,b),利用導函數規則(a+b)’=a’+b’,對該子樹求導的僞代碼如下:
dao(+,a,b)
return new root(+,dao(a),dao(b))
上面是隻有“+”一種情況,在具體代碼中我們需要根據所有符號的導函數規則分情況處理。
最終,將導函數樹進行遞歸計算。
至此我們得到一個表達式 ,一個表達式的導 。
利用牛頓法迭代處理,最終求解出未知數。