牛頓法求解一元函數

牛頓法求解一元函數

對於一個簡單的一元方程我們可以通過代數運算來求解,但是對於一個非線性的複雜一元函數例如2x58x2+sin(xx)2x=0 這樣的方程,想要通過人力計算就很難辦到。
下面介紹利用牛頓法來構建的一個一元函數方程求解的程序。

牛頓法

當方程沒有求根公式或者求根公式特別複雜時,利用牛頓法都可以進行迭代求解。
維基對牛頓法的定義爲:

它是一種在實數域和複數域上近似求解方程的方法。方法使用函數f(x)的泰勒級數的前面幾項來尋找方程 f(y)=0的根。

先理解下泰勒級數
如果一個函數是以實數或者複數爲變量,並且該函數是可導的,那麼這個函數就可以被展開爲泰勒多項式。
有如下定理:

設 n 是一個正整數。如果定義在一個包含 a 的區間上的函數 f 在 a 點處 n+1 次可導,那麼對於這個區間上的任意 x,都有:
f(x)=f(a)+f(a)1!(xa)+f(2)(a)2!(xa)2+...+f(n)(a)n!(xa)n+Rn(x)
其中的多項式稱爲函數在a 處的泰勒展開式,剩餘的Rn(x) 是泰勒公式的餘項,是(xa)n 的高階無窮小。

牛頓法就是利用一階泰勒展開f(x)=f(a)+(xa)f(a) ,對f(x)=0 求解得到x=af(a)f(a) ,利用其對x 進行不斷的迭代,直到x 收斂到正確的解或者逼近正確的解。
迭代公式爲:xn+1=xnfxnf(xn)
用一段僞代碼表示爲:

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))的表達式樹:
這裏寫圖片描述
該樹的特點是:葉子節點爲操作數,父節點爲操作符,可以從葉子節點開始一直向上遞歸最終求解出整個表達式的結果。

構建表達式樹

利用後綴表達式很容易的得出表達式樹,其構建的算法如下:

  • 新建一個棧,該棧保存的是一個表達樹
  • 從頭到尾遍歷後綴表達式,
    • 當發現操作數時,我們將其存爲操作數節點,入棧;
    • 當發現操作符時,我們將其存爲操作符節點,
      • 若是二元操作符,我們將棧頂的兩個節點彈出,作爲該操作符的節點左右節點,然後將該子樹入棧
      • 若是一元操作符,我們將棧頂節點彈出,作爲該操作符的右節點,然後將該子樹入棧

最終棧底只有一個表達式樹,其他情況均視爲表達式錯誤。

求值

以根結點爲操作符,左右子樹爲操作數,可以對二叉樹進行遞歸求解,最終得到一個含有未知數x 的簡化表達式。

表達式樹的求導和計算

對於上面得到的二叉表達樹,由於二叉表達樹根結點總是操作符,左右子樹總是表達式,因此可以對二叉表達樹進行遞歸求導。求導的過程也是一個遞歸的構建導函數樹的過程。
例如:一個子樹是這樣的
【圖片a+b】
設計一個導函數dao(+,a,b),利用導函數規則(a+b)’=a’+b’,對該子樹求導的僞代碼如下:

dao(+,a,b)
   return new root(+,dao(a),dao(b))

上面是隻有“+”一種情況,在具體代碼中我們需要根據所有符號的導函數規則分情況處理。
最終,將導函數樹進行遞歸計算。

至此我們得到一個表達式t ,一個表達式的導dt
利用牛頓法迭代處理,最終求解出未知數。


完整代碼的Github地址。
最終的運行效果地址點此鏈接
如有錯誤或者改進的地方,歡迎指出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章