解方程 ( 迭代法/牛頓迭代/高斯消元 ) 詳解及模板

歡迎訪問https://blog.csdn.net/lxt_Lucia~~

宇宙第一小仙女\(^o^)/~~萌量爆表求帶飛=≡Σ((( つ^o^)つ~ dalao們點個關注唄~~

 


 

一.迭代法解方程 ( 組 ) 的根

 

本篇一、二部分轉自“星博”:https://blog.csdn.net/Akatsuki__Itachi/article/details/80719686

 

首先,迭代法解方程的實質是按照下列步驟構造一個序列x0,x1,…,xn,來逐步逼近方程f(x)=0的解:

1)選取適當的初值x0;

2)確定迭代格式,即建立迭代關係,需要將方程f(x)=0改寫爲x=φ(x)的等價形式;

3)   構造序列x0,x1,……,xn,即先求得x1=φ(x0),再求x2=φ(x1),……如此反覆迭代,就得到一個數列x0, x1,……,xn,若這個數列收斂,即存在極值,且函數 φ(x)連續,則很容易得到這個極限值,x*就是方程f(x)=0的根。

 

舉個例子:

求解方程: f(x) =x^3-x-1=0  在區間 (1,1.5)內的根。

首先我們將方程寫成這種形式:

用初始根x0=1.5帶入右端,可以得到

這時,x0和x1的值相差比較大,所以我們要繼續迭代求解,將x1再帶入公式得

直到我們我們得到的解的序列收斂,即存在極值的時候,迭代結束。

下面是這個方程迭代的次數以及每次xi的解(i=0,1,2....)

 

我們發現當k=7和8的時候,方程的解已經不再發生變化了,這時候我們就得到了此方程的近似解。

算法核心:

#define eps 1e-8
int main()
{
    x0=初始近似根;
    do{
        x1=x0;
        x0=g(x1); //按特定的方程計算新的近似根
    }while(fabs(x0-x1)>Epsilon);
    printf("方程的近似根是%f\n",x0);
}

 

注意:如果方程無解,算法求出的近似根序列就不會收斂,那麼迭代過程就會變成死循環。因此,在使用迭代算法前應先考察方程是否有解,並在算法中對迭代次數給予限制。

 

下面再寫一個求解方程組的例子加深一下理解:

 

算法說明:

方程組解的初值X=(x0,x1,…,xn-1),迭代關係方程組爲:xi=gi(X)(i=0,1,…,n-1),w爲解的精度,maxn爲迭代次數。

算法如下:

int main()
{
    for (i=0;i<n;i++)   
        x[i]=初始近似根
    do{
        k=k+1;
        for(i=0;i<n;i++)        
            y[i]=x[i];         
        for(i=0;i<n;i++)
            x[i]=gi(X);   //按特定的方程計算新的近似根
        for(i=0;i<n;i++)   
            c=c+fabs(y[i]-x[i]);//c要每次重新設初值爲0
    }while(c>w and k<maxn );  
    for(i=0;i<n;i++)        
        print("變量的近似根是",x[i]);
}

選取初始向量 

精確度爲1e-8,迭代次數爲10.

求解代碼如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define eps 1e-8
using namespace std;
const int maxn=100;
double x[10],y[10];
int main()
{
    for(int i=1;i<=4;i++)
        x[i]=0;
    int cnt=0;
    double c=0;
    do{
        for(int i=1;i<=4;i++)
            y[i]=x[i];
        for(int i=1;i<=4;i++)
        {
            x[1]=(6+x[2]-2*x[3])/10;
            x[2]=(25+x[1]+x[3]-3*x[4])/11;
            x[3]=(-11-2*x[1]+x[2]+x[4])/10;
            x[4]=(15-3*x[2]+x[3])/8;
        }
        c=0;
        for(int i=1;i<=4;i++)
            c+=(fabs(y[i]-x[i]));
    }while(c>eps&&cnt<maxn);
    for(int i=1;i<=4;i++)
        printf("x%d = %.4lf\n",i,x[i]);
}

運行結果如下:

 

迭代法求解方程的過程是多樣化的,比如牛頓迭代法,二分逼近法求解等。


 

二.牛頓迭代法

 

下面就是效率比較高且比較常用的牛頓迭代法

牛頓迭代法又稱爲切線法,它比一般的迭代法有更高的收斂速度,如下圖所示。

首先, 選擇一個接近函數f(x)零點的x0, 計算相應的f(x0)和切線斜率f'(x0)(這裏f '  表示函數f的導數)。然後我們計算穿過點   (x0,f (x0))且斜率爲f '(x0)的直線方程

和x軸的交點的x的座標,也就是求如下方程的解

將新求得交點的x座標命名爲x1。如圖4所示,通常x1會比x0更接近方程f(x) = 0的解。接下來用x1開始下一輪迭代 .

迭代公式可化簡爲:

上式就是有名的牛頓迭代公式。已經證明, 如果f'  是連續的, 並且待求的零點x是孤立的, 那麼在零點x周圍存在一個區域, 只要初始值x0位於這個鄰近區域內, 那麼牛頓法必定收斂。

 

求形如ax^3+bx^2+cx+d=0方程根的算法,係數a、b、c、d的值依次爲1、2、3、4,由主函數輸入。求x在1附近的一個實根。求出根後由主函數輸出。

 

由以上的公式可得到代碼:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define eps 1e-8
using namespace std;
int main()
{
    double a,b,c,d;
    cin>>a>>b>>c>>d;
    double x1=1,x,f,fx;
    do{
        x=x1;
        f=((a*x+b)*x+c)*x+d;
        fx=(3*a*x+2*b)*x+c;
        x1=x-f/fx;
    }while(fabs(x1-x)>=eps);
    printf("%.2lf",x1);
}

結果如下:

 


 

三.二分逼近法

 

接下來說一下二分逼近法:

用二分法求解方程f(x)=0根的前提條件是:f(x)在求解的區間[a,b]上是連續的,且已知f(a)與f(b)異號,即 f(a)*f(b)<0。

令[a0,b0]=[a,b],c0=(a0+b0)/2,若f(c0)=0,則c0爲方程f(x)=0的根;否則,若f(a0)與f(c0)異號,即 f(a0)*f(c0)<0,則令[a1,b1]=[a0,c0];若f(b0)與f(c0)異號,即 f(b0)*f(c0)<0,則令[a1,b1]=[c0,b0]。

 依此做下去,當發現f(cn)=0時,或區間[an,bn]足夠小,比如| an-bn |<0.0001時,就認爲找到了方程的根。

 

例:

用二分法求一元非線性方程f(x)= x^3/2+2x^2-8=0(其中^表示冪運算)在區間[0,2]上的近似實根r,精確到0.0001.

算法如下:

int main( )
{
    double x,x1=0,x2=2,f1,f2,f;
    print(“input a,b (f(a)*f(b)<0)”);
    input(a,b);
    f1=x1*x1*x1/2+2*x1*x1-8;
    f2=x2*x2*x2/2+2*x2*x2-8;
    if(f1*f2>0)
    {
        printf("No root");
        return;
    }
    do{
        x=(x1+x2)/2;
        f=x*x*x/2+2*x*x-8;
        if(f=0)
            break;
        if(f1*f>0.0)
        {
            x1=x;
            f1=x1*x1*x1/2+2*x1*x1-8;
        }
        else
            x2=x;
    }
    while(fabs(f)>=1e-4);
    print("root=",x);
}

 

 


 

三.高斯消元入門詳解及模板

 

此部分主要講述解方程的方法,便於以後查找,轉載於dalao博客:https://blog.csdn.net/pengwill97/article/details/77200372,先感謝大佬。

 

1、基本描述

學習一個算法/技能,首先要知道它是幹什麼的,那麼高斯消元是幹啥的呢?

高斯消元主要用來求解線性方程組,也可以求解矩陣的秩,矩陣的逆。在ACM中是一個有力的數學武器.

它的時間複雜度是n^3,主要與方程組的個數,未知數的個數有關。

那麼什麼是線性方程組呢? 
簡而言之就是有多個未知數,並且每個未知數的次數均爲一次,這樣多個未知數組成的方程組爲線性方程組。

2、算法過程

其實高斯消元的過程就是手算解方程組的過程,回憶一下小的時候怎麼求解方程組:加減消元,消去未知數,如果有多個未知數,就一直消去,直到得到類似kx=b(k和b爲常數,x爲未知數)的式子,就可以求解出未知數x,然後我們回代,依次求解出各個未知數的值,就解完了方程組。 
換句話說,分兩步: 
1). 加減消元 
2). 回代求未知數值

高斯消元就是這樣的一個過程。 
下面通過一個小例子來具體說明

0).求解方程組

有這樣一個三元一次方程組: 

⎧⎩⎨⎪⎪2x+y+z=16x+2y+z=−1−2x+2y+z=7①②③{2x+y+z=1①6x+2y+z=−1②−2x+2y+z=7③

 

1).消去x

①×(−3)+②①×(−3)+②得到 
0x−y−2z=−40x−y−2z=−4

①+③①+③得到 
0x+3y+2z=80x+3y+2z=8

從而得到 

⎧⎩⎨⎪⎪2x+y+z=10x−y−2z=−40x+3y+2z=8①②③{2x+y+z=1①0x−y−2z=−4②0x+3y+2z=8③

 

2).消去y

②×3+③②×3+③得到 
0x+0y−4z=−40x+0y−4z=−4

進而得到 

⎧⎩⎨⎪⎪2x+y+z=10x−y−2z=−40x+0y−4z=−4①②③{2x+y+z=1①0x−y−2z=−4②0x+0y−4z=−4③

 

至此,我們已經求解出來了 

z=1z=1

 

下一步我們進行回代過程

3).回代求解y

將z=1z=1帶入②②,求得 

y=2y=2


進而得到 

⎧⎩⎨⎪⎪2x+y+z=1y=2z=1①②③{2x+y+z=1①y=2②z=1③

 

4).回代求解x

將z=1,y=2z=1,y=2帶入①①,求得 

x=−1x=−1

 

最終得到 

⎧⎩⎨⎪⎪x=−1y=2z=1①②③{x=−1①y=2②z=1③

 

至此,整個方程組就求解完畢了。

 

3、再解算法

對於方程組,其係數是具體存在矩陣(數組)裏的,下面在給出實際在矩陣中的表示(很熟悉就可以跳過不看啦~)

 

0).求解方程組

 

⎡⎣⎢⎢⎢⎢x26−2y122z111val117⎤⎦⎥⎥⎥⎥[xyzval21116211−2217]

 

1).消去x

 

⎡⎣⎢⎢⎢⎢x200y1−13z1−22val1−48⎤⎦⎥⎥⎥⎥[xyzval21110−1−2−40328]

 

2).消去y

 

⎡⎣⎢⎢⎢⎢x200y1−10z1−2−4val1−4−4⎤⎦⎥⎥⎥⎥[xyzval21110−1−2−400−4−4]

 

3).回代求解y

回代的時候,記錄各個變量的結果將保存在另外一個數組當中,故保存矩陣的數組值不會發生改變,該矩陣主要進行消元過程。 

⎡⎣⎢⎢⎢⎢x200y1−10z1−2−4val1−4−4⎤⎦⎥⎥⎥⎥[xyzval21110−1−2−400−4−4]

 

4、再再解算法

說了這麼多,其實有一些情況我們還沒有說到。 
通過上述的消元方法,其實我們比較希望得到的是一個上三角陣(省去了最後的val) 

⎡⎣⎢⎢2001−101−2−4⎤⎦⎥⎥[2110−1−200−4]

 

下面問題來了: 
Q1:係數不一定是整數啊? 
A1:這時候數組就要用到浮點數了!不能是整數!

Q2:什麼時候無解啊? 
A2:消元完了,發現有一行係數都爲0,但是常數項不爲0,當然無解啦!比如: 

⎡⎣⎢⎢⎢⎢x200y1−10z1−20val1−45⎤⎦⎥⎥⎥⎥[xyzval21110−1−2−40005]

 

Q3:什麼時候多解啊? 
A3:消元完了,發現有好幾行係數爲0,常數項也爲0,這樣就多解了!有幾行爲全爲0,就有幾個自由元,所謂自由元,就是這些變量的值可以隨意取,有無數種情況可以滿足給出的方程組,比如: 

⎡⎣⎢⎢⎢⎢x200y100z100val100⎤⎦⎥⎥⎥⎥[xyzval211100000000]


您說這x,y,z不是無數組解嘛!

 

Q4:那什麼時候解是唯一的啊! 
A4:您做一下排除法,不滿足2和3的,不就是解釋唯一的嘛!其實也就是說我們的係數矩陣可以化成上三角陣

 

5、代碼實現

囉裏囉嗦說了一堆,想必算法的流程已經熟悉了,那麼高斯消元如何用代碼實現呢? 

戳這裏有更多類型的 高斯消元模板

(以下參考kuangbin大牛的模板)

 

 

 

#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<string.h>
#include<math.h>
using namespace std;
const int MAXN=50;
int a[MAXN][MAXN];//增廣矩陣
int x[MAXN];//解集
bool free_x[MAXN];//標記是否是不確定的變元
int gcd(int a,int b)
{
    if(b == 0) return a;
    else return gcd(b,a%b);
}
inline int lcm(int a,int b)
{
    return a/gcd(a,b)*b;//先除後乘防溢出
}
// 高斯消元法解方程組(Gauss-Jordan elimination).(-2表示有浮點數解,但無整數解,
//-1表示無解,0表示唯一解,大於0表示無窮解,並返回自由變元的個數)
//有equ個方程,var個變元。增廣矩陣行數爲equ,分別爲0到equ-1,列數爲var+1,分別爲0到var.
int Gauss(int equ,int var)
{
    int i,j,k;
    int max_r;// 當前這列絕對值最大的行.
    int col;//當前處理的列
    int ta,tb;
    int LCM;
    int temp;
    for(int i=0; i<=var; i++)
    {
        x[i]=0;
        free_x[i]=true;
    }

    //轉換爲階梯陣.
    col=0; // 當前處理的列
    for(k = 0; k < equ && col < var; k++,col++) // 枚舉當前處理的行.
    {
        // 找到該col列元素絕對值最大的那行與第k行交換.(爲了在除法時減小誤差)
        max_r=k;
        for(i=k+1; i<equ; i++)
        {
            if(abs(a[i][col])>abs(a[max_r][col]))
                max_r=i;
        }
        if(max_r!=k) // 與第k行交換.
        {
            for(j=k; j<var+1; j++)
                swap(a[k][j],a[max_r][j]);
        }
        if(a[k][col]==0) // 說明該col列第k行以下全是0了,則處理當前行的下一列.
        {
            k--;
            continue;
        }
        for(i=k+1; i<equ; i++) // 枚舉要刪去的行.
        {
            if(a[i][col]!=0)
            {
                LCM = lcm(abs(a[i][col]),abs(a[k][col]));
                ta = LCM/abs(a[i][col]);
                tb = LCM/abs(a[k][col]);
                if(a[i][col]*a[k][col]<0)
                    tb=-tb;//異號的情況是相加
                for(j=col; j<var+1; j++)
                    a[i][j] = a[i][j]*ta-a[k][j]*tb;
            }
        }
    }
    // 1. 無解的情況: 化簡的增廣陣中存在(0, 0, ..., a)這樣的行(a != 0).
    for (i = k; i < equ; i++)  // 對於無窮解來說,如果要判斷哪些是自由變元,那麼初等行變換中的交換就會影響,則要記錄交換.
    {
        if (a[i][col] != 0)
            return -1;
    }
    // 2. 無窮解的情況: 在var * (var + 1)的增廣陣中出現(0, 0, ..., 0)這樣的行,即說明沒有形成嚴格的上三角陣.
    // 且出現的行數即爲自由變元的個數.
    if (k < var)
    {
        return var - k; // 自由變元有var - k個.
    }
    // 3. 唯一解的情況: 在var * (var + 1)的增廣陣中形成嚴格的上三角陣.
    // 計算出Xn-1, Xn-2 ... X0.
    for (i = var - 1; i >= 0; i--)
    {
        temp = a[i][var];
        for (j = i + 1; j < var; j++)
        {
            if (a[i][j] != 0)
                temp -= a[i][j] * x[j];
        }
        if (temp % a[i][i] != 0)
            return -2; // 說明有浮點數解,但無整數解.
        x[i] = temp / a[i][i];
    }
    return 0;
}
int main(void)
{
    int i, j,equ,var;
    while (scanf("%d %d", &equ, &var) != EOF)
    {
        memset(a, 0, sizeof(a));
        for (i = 0; i < equ; i++)
            for (j = 0; j < var + 1; j++)
                scanf("%d", &a[i][j]);
        int free_num = Gauss(equ,var);
        if (free_num == -1)
            printf("無解!\n");
        else if (free_num == -2)
            printf("有浮點數解,無整數解!\n");
        else if (free_num > 0)
        {
            printf("無窮多解! 自由變元個數爲%d\n", free_num);
            for (i = 0; i < var; i++)
            {
                if (free_x[i])
                    printf("x%d 是不確定的\n", i + 1);
                else
                    printf("x%d: %d\n", i + 1, x[i]);
            }
        }
        else
        {
            for (i = 0; i < var; i++)
                printf("x%d: %d\n", i + 1, x[i]);
        }
        printf("\n");
    }
    return 0;
}

 

 

 

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