1. 程式人生 > 其它 >線性代數——高斯消元

線性代數——高斯消元

線性代數——高斯消元


第一板塊

首先,我們先來講解一下線性代數:

  1. 什麼是線性代數

    函式研究的是,輸入一個數,經過函式運算 後,產出一個數。而有時候我們研究的問題太複雜,需要輸入多個數,經過運算後,就會產出多個數。這時候,線性代數應運而生。

    多個數,我們可以用括號括起來,形成一個數組。在幾何學上,陣列被稱作向量,向量就是一個有方向有大小的直線段。

    所以,線性代數就是:輸入一段直線,經過加工後,產出一段直線

    與函式相似,用圖來描述就是:

  2. 那麼矩陣是怎麼對直線加工的呢?

    假設輸入的直線為【1,2】,用於加工的矩陣為【0, 1,-1, 0】,

    那麼這個矩陣的加工過程就是把i換成矩陣中的第一列,把j換成第二列,然後再以新的基向量為原料,重新利用【1,2】拼湊一個新的向量。

  3. 我們可以用熟悉的口訣“左行乘後列”來檢驗一下:

  4. 同理,稍微複雜一些的三維向量遇到三維矩陣後的加工過程:

  5. 行列式是什麼?

    矩陣對向量進行加工,行列式能夠描述這種加工作用的強弱

    上文提到,矩陣對向量加工是通過改變基向量來實現的。以二維為例,預設的基向量張成的面積為1,經過變換後形成的新的基向量張成的面積變為了S,那麼這個矩陣的行列式為S

  6. 有時候,矩陣的行列式會為0,這就說明新的基向量張成的面積為0,也就說明新的基向量發生了重合。

    有時候,矩陣的行列式為負數,說明線性空間發生了翻轉。換句話來說,預設的兩個基向量,j在i的逆時針方向,經過加工後,線性空間發生了翻轉,導致i在j的逆時針方向。

  7. 什麼是單位矩陣?

    就是說無論給它輸入什麼樣的向量,產生的向量都與原來的相同

    既然矩陣對向量的加工作用是通過改變基向量來實現的,那麼如果想保持你的輸入和輸出相等,只需要保證矩陣不會改變基向量即可

    so,二階、三階、以及n階單位矩陣可寫為:

  8. 5.什麼是逆矩陣?

    我們結合圖來分析:

    如果說上圖向量1等於向量3,那麼就說明,向量經過矩陣1和矩陣2後又變成了自己。也就是說,矩陣1和矩陣2的加工作用是相反的(對著幹),那麼我們就說矩陣1和矩陣2是逆矩陣

    明白了原理,那麼解逆矩陣就容易了:

    ****這裡補充一下:行列式為0的矩陣是沒有逆矩陣的

    因為如果為0,表明矩陣在對向量轉換的過程中,將向量空間壓縮到了一個更低的維度上。

    向量降維後,它就無法還原成原來的樣子了。 你就好比有一個三維的長方體,從大部分角度來觀察,都是一個三維結構,但是,當你正視俯視側視時,你只能觀察到一個二維矩形。我們是無法通過這個二維矩形的樣子,來推測出原來的長方體的。

    6.什麼是秩?

    矩陣可以將一個向量進行加工,變成另外一個向量。

    就比如說,一個3階矩陣,可以對多個三維向量進行加工,變成多個新三維向量

    有時候,所有的這些新三維向量,最終都落在一條直線上,即1維

    有時候,所有的這些新三維向量,最終都落在一個二維平面上,即2維

    有時候,所有的這些新三維向量,最終都落三維空間上,即3維

    以上三種情況分別對應秩為1,2,3.

    總而言之,秩就是描述這個矩陣會不會將輸入的向量空間降維。若沒有,則稱為滿秩。

    7.什麼是特徵向量、特徵值?

    矩陣能夠對向量進行加工,變成一個新的向量

    但有時候會出現這種情況:

    對於某一個矩陣,輸入一個向量後,經過加工後,新生成的向量與原來的向量是共線的。那也就是說,這個矩陣在加工的過程中並沒有改變其方向

    但是雖然不會被改變方向,但是大小改變了,新的向量長度是原來向量長度的λ倍這個λ就是特徵向量的特徵值



第二板塊

明白了什麼是線性代數後,我們開始今天的重點——高斯消元(好像前面講的沒有什麼用·····,但是打了這麼久,就當做給你們普及知識了 調皮 啦啦啦)


簡介


數學上,高斯消元法,是線性代數規劃中的一個演算法,可用來為線性方程組求解。但其演算法十分複雜,不常用於加減消元法,求出矩陣的秩,以及求出可逆方陣的逆矩陣。不過,如果有過百萬條等式時,這個演算法會十分省時。一些極大的方程組通常會用迭代法以及花式消元來解決。當用於一個矩陣時,高斯消元法會產生出一個“行梯陣式”。高斯消元法可以用在電腦中來解決數千條等式及未知數。亦有一些方法特地用來解決一些有特別排列的係數的方程組。(出自百度。。)


原理


原理其實很簡單,就是消元加減消元&代入消元),所謂消元法,就是將方程組中的一方程的未知數用含有另一未知數的代數式表示,並將其代入到另一方程中,這就消去了一未知數,得到一解;或將方程組中的一方程倍乘某個常數加到另外一方程中去,也可達到消去一未知數的目的。主要用於二元一次方程組的求解。

它的核心呢,就是:

  1. 兩方程互換,解不變;

  2. 一方程乘以非零數k,解不變;

  3. 一方程乘以數k加上另一方程,解不變;


分析


obviously,高斯消元是可以用來求n元一次方程組的,它的主要思想就是把一個 n∗(n+1) 的矩陣的對角線消成 1,除了第 n+1 列(用來存放 常數項 的)的其他全部元素消成 0(方程的係數)

以洛谷的模板題為例(您們可以刷一下)

這個三元一次方程組就可以寫成如下3 * 4矩陣:

之後運用矩陣的性質(上文提到的核心) 來把矩陣消成對角線上的元素為 1,並且除了第 n+1 列其餘元素均為 0 的矩陣,

這樣我們就很容易的得出每個未知數的值:分別是從上到下第 n+1 列的值(因為這時候每個未知數的係數都為 1,貌似很簡單的樣子)

那麼該如何消呢?(敲黑板!重點來了)

還是以上面的矩陣舉例(再放一遍圖片)

上面我們提到了要把矩陣消成對角線為 1,除了第 n+1 列其餘元素都為 0。那麼換句話來說,就是每一列都至少有一個元素不為0,因為若有一列全為0的話,肯定有第i行第i列消不成1,此時是無解的

我們來證明一下:站在方程組的數學角度上來想,我們把每個未知數的係數寫成矩陣,所以矩陣的某一列就是某一未知數的全部係數,

如果全為 0,那麼不就是沒有這個未知數嗎?那麼這個未知數的值就不能確定了,那不就是無解嗎?

ok,明白之後我們就能進行初步判斷了:

for(int i=1;i<=n;i++)
    {
        max=i;                      //從第i行開始往下找,一直找到一個第i列不為0的行
        while(a[max][i]==0&&max<=n) 
        max++;                                    
         // 判斷第i列元素非0的最上行,因為第i行第i列元素不能為0 
        if(max==n+1) { //一直判到了n+1行,可是一共才只有n行,說明有一列全為0,無解     
		cout<<"No Solution";
		return 0;
	}    
       
        for(int j=1;j<=n+1;j++)            
                //將第i行元素與第max行第i列不為0的那一行與當前行交換 
        swap(a[i][j],a[pl][j]);   //保證第i行第i列不為0
        }

這樣之後,我們就保證了第 i 行第 i 列的元素不為 0,可是我們要讓第 i 行第 i 列的值整成 1 啊,我們可以用性質 (3),讓第i行的每個元素都除以第 i 行第 i 列的值

注意:這裡用到了除法,就有可能出現小數,所以我們要用 double 型別定義二維陣列矩陣

	double temp=a[i][i];                         
        for(int j=1;j<=n+1;j++)
        a[i][j]=a[i][j]/temp;                         

我們就讓第 i 行第 i 列的元素搞成 1 列,繼續完成接下來的任務:順便把第 i 列的其他元素搞成 0;

我們已經把第 i 行的搞成了1,所以我們只需要把其餘行的每個元素都減去本行的首元素即可(即第 i 行的對應元素)(為什麼是第 i 行呢?因為第 i 行第 i 列的元素是 1 啊, 比較好消)

程式碼如下:

#include<bits/stdc++.h>
using namespace std;
int n;
double a[1010][1010];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
       for(int j=1;j<=n+1;j++)
       cin>>a[i][j];
    for(int i=1;i<=n;i++)
    {
        int max=i;
        while(a[max][i]==0&&max<=n) 
        max++;                                    
         // 判斷第i列首元素非0的最上行,因為第i行第i列元素不能為0 
        if(max==n+1) {
			cout<<"No Solution";
			return 0;
		}    
        //一直判到了n+1行,可是一共才只有n行,說明有一列全為0,無解 
        for(int j=1;j<=n+1;j++)             //將第i行第i列元素不為0的那一行與當前行交換 
        swap(a[i][j],a[max][j]);
        double temp=a[i][i];                          //讓第i行每個元素都除以a[i][i]使得a[i][i]為1 
        for(int j=1;j<=n+1;j++)
        a[i][j]=a[i][j]/temp;   
        for(int j=1;j<=n;j++)
        {
            if(i!=j)                        //將第i列除了第i行的元素全消成0 
            {                               //方法是第j行每個元素a[j][k]都減去a[j][1]*a[i][k] 
                double t=a[j][i];
                for(int k=1;k<=n+1;k++)
                a[j][k]=a[j][k]-t*a[i][k];
            }
        }
    }
    for(int i=1;i<=n;i++)
    printf("%.2lf\n",a[i][n+1]);
    return 0;
}

完結撒花

好吧,並沒有。。。

突然發現這題用高斯約旦消元更簡單

高斯約旦消元同高斯消元大體差不多,但消元時上面計算過的行也要消去當前列,最後得到的是對角矩陣而不是上三角矩陣。

相對於傳統的高斯消元,約旦消元法的精度更好、程式碼更簡單,沒有迴帶的過程。

約旦消元法大致思路如下:

1.選擇一個尚未被選過的未知數作為主元,選擇一個包含這個主元的方程。

2.將這個方程主元的係數化為1。

3.通過加減消元,消掉其它方程中的這個未知數。

4.重複以上步驟,直到把每一行都變成只有一項有係數。

我們用矩陣表示每一項係數以及結果

程式碼如下:

#include<bits/stdc++.h>
using namespace std;
double a[300][300];
int n;
int main()
{
	cin>>n;
	for( int i=1;i<=n;i++)
	{
		for( int j=1;j<=n+1;j++)
		{
			cin>>a[i][j];
		}
	}
	for( int i=1;i<=n;i++)//列舉列(項) 
	{
		 int max=i;
		for( int j=i+1;j<=n;j++)//找出每一列的最大主元 (最大項)
		{
			if(fabs(a[j][i])>fabs(a[max][i]))//找尋最大主元 (最大項) 
            //fabs是取浮點數的絕對值的函式
			{
				max=j;
			}
		}
		if(i^max)//相當於i!=max,保證不在當前行 
		{
			swap(a[i],a[max]);//交換行 
		}
		if(!a[i][i])//主元等於0則說明該列都為0,肯定無解 
		{
			cout<<"No Solution"<<endl;
			return 0;
		}
		for( int j=1;j<=n;j++)//每一項都減去一個數(即加減消元)
		{
			if(j!=i)//對角主元不變 
			{
				 double temp=a[j][i]/a[i][i];
				for( int k=i;k<=n+1;k++)
				{
					a[j][k]-=a[i][k]*temp;
				}
			}
		}
	}

	for( int i=1;i<=n;i++)
	{
		printf("%.2lf\n",a[i][n+1]/a[i][i]);
	}
	return 0;
}

完結撒花~~~~(這次是真完結了)

寫的不好,如有錯誤,請dalao指出······