線性代數——高斯消元
線性代數——高斯消元
第一板塊
首先,我們先來講解一下線性代數:
-
什麼是線性代數?
函式研究的是,輸入一個數,經過函式運算 後,產出一個數。而有時候我們研究的問題太複雜,需要輸入多個數,經過運算後,就會產出多個數。這時候,線性代數應運而生。
多個數,我們可以用括號括起來,形成一個數組。在幾何學上,陣列被稱作向量,向量就是一個有方向有大小的直線段。
所以,線性代數就是:輸入一段直線,經過加工後,產出一段直線
與函式相似,用圖來描述就是:
-
那麼矩陣是怎麼對直線加工的呢?
假設輸入的直線為【1,2】,用於加工的矩陣為【0, 1,-1, 0】,
那麼這個矩陣的加工過程就是把i換成矩陣中的第一列,把j換成第二列,然後再以新的基向量為原料,重新利用【1,2】拼湊一個新的向量。
-
我們可以用熟悉的口訣“左行乘後列”來檢驗一下:
-
同理,稍微複雜一些的三維向量遇到三維矩陣後的加工過程:
-
行列式是什麼?
矩陣對向量進行加工,行列式能夠描述這種加工作用的強弱
上文提到,矩陣對向量加工是通過改變基向量來實現的。以二維為例,預設的基向量張成的面積為1,經過變換後形成的新的基向量張成的面積變為了S,那麼這個矩陣的行列式為S
-
有時候,矩陣的行列式會為0,這就說明新的基向量張成的面積為0,也就說明新的基向量發生了重合。
有時候,矩陣的行列式為負數,說明線性空間發生了翻轉。換句話來說,預設的兩個基向量,j在i的逆時針方向,經過加工後,線性空間發生了翻轉,導致i在j的逆時針方向。
-
什麼是單位矩陣?
就是說無論給它輸入什麼樣的向量,產生的向量都與原來的相同
既然矩陣對向量的加工作用是通過改變基向量來實現的,那麼如果想保持你的輸入和輸出相等,只需要保證矩陣不會改變基向量即可
so,二階、三階、以及n階單位矩陣可寫為:
-
5.什麼是逆矩陣?
我們結合圖來分析:
如果說上圖向量1等於向量3,那麼就說明,向量經過矩陣1和矩陣2後又變成了自己。也就是說,矩陣1和矩陣2的加工作用是相反的(對著幹),那麼我們就說矩陣1和矩陣2是逆矩陣
明白了原理,那麼解逆矩陣就容易了:
****這裡補充一下:行列式為0的矩陣是沒有逆矩陣的
因為如果為0,表明矩陣在對向量轉換的過程中,將向量空間壓縮到了一個更低的維度上。
向量降維後,它就無法還原成原來的樣子了。 你就好比有一個三維的長方體,從大部分角度來觀察,都是一個三維結構,但是,當你正視俯視側視時,你只能觀察到一個二維矩形。我們是無法通過這個二維矩形的樣子,來推測出原來的長方體的。
6.什麼是秩?
矩陣可以將一個向量進行加工,變成另外一個向量。
就比如說,一個3階矩陣,可以對多個三維向量進行加工,變成多個新三維向量
有時候,所有的這些新三維向量,最終都落在一條直線上,即1維
有時候,所有的這些新三維向量,最終都落在一個二維平面上,即2維
有時候,所有的這些新三維向量,最終都落三維空間上,即3維
以上三種情況分別對應秩為1,2,3.
總而言之,秩就是描述這個矩陣會不會將輸入的向量空間降維。若沒有,則稱為滿秩。
7.什麼是特徵向量、特徵值?
矩陣能夠對向量進行加工,變成一個新的向量
但有時候會出現這種情況:
對於某一個矩陣,輸入一個向量後,經過加工後,新生成的向量與原來的向量是共線的。那也就是說,這個矩陣在加工的過程中並沒有改變其方向
但是雖然不會被改變方向,但是大小改變了,新的向量長度是原來向量長度的λ倍這個λ就是特徵向量的特徵值
第二板塊
明白了什麼是線性代數後,我們開始今天的重點——高斯消元(好像前面講的沒有什麼用·····,但是打了這麼久,就當做給你們普及知識了 調皮 啦啦啦)
簡介
數學上,高斯消元法,是線性代數規劃中的一個演算法,可用來為線性方程組求解。但其演算法十分複雜,不常用於加減消元法,求出矩陣的秩,以及求出可逆方陣的逆矩陣。不過,如果有過百萬條等式時,這個演算法會十分省時。一些極大的方程組通常會用迭代法以及花式消元來解決。當用於一個矩陣時,高斯消元法會產生出一個“行梯陣式”。高斯消元法可以用在電腦中來解決數千條等式及未知數。亦有一些方法特地用來解決一些有特別排列的係數的方程組。(出自百度。。)
原理
原理其實很簡單,就是消元(加減消元&代入消元),所謂消元法,就是將方程組中的一方程的未知數用含有另一未知數的代數式表示,並將其代入到另一方程中,這就消去了一未知數,得到一解;或將方程組中的一方程倍乘某個常數加到另外一方程中去,也可達到消去一未知數的目的。主要用於二元一次方程組的求解。
它的核心呢,就是:
-
兩方程互換,解不變;
-
一方程乘以非零數k,解不變;
-
一方程乘以數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指出······