眾所周知,高斯消消樂很好玩(並不),並且理解很簡單,代碼也很“簡單”(蒟蒻到聽了n遍還是不會寫代碼,在此寫篇博客記下來)
高斯消元:
高斯消元是用來解線性方程組的,即把一個方程組的系數與方程右邊的數寫成一個矩陣,再解這個矩陣對應的行列式的值,就可以快速的求解
一個行列式有如下幾種初等變換:
1.交換兩行(列),行列式的值變號。
2.某行(列)的所有數,同時乘上k,行列式的值相當於原來的k倍(但某行(列)加上某個數的k倍值是不變的)
3.某行(列)的所有數同時加上a,行列式的值不變
so高斯消元應該怎么消呢?
(高斯消元以列為單位消)
step1:先將第一行第一列的數化為1,並且同一行的數做相同變換
step2:再將其余行的第一列化為0(通過加/減1的k倍),並且同一行的數做相同變換
step3:這時,第一行就處理完了,再將原本的第二行視作新的第一行,且新的第一列為原本的第二列(就是把原來矩陣的第一行第一列扔掉),做相同的變換,一直處理到原本的第n-1行,這樣矩陣就消完了。接下來就是回帶求解
當然,如果有一行全是0的時候會出現無解或有無窮多解,要特判。
因為這是解方程用的,所以輸入的矩陣每一行格式如下:
系數1 系數2....................系數n 等號右邊的數(這點很重要)
總體格式如下:
|a11................a1n b1|
|a21...............a2n b2|
|...................................|
|an1...............ann bn|
其中a都是系數,b為等號右邊的數
高斯消元的目的是把所有的a組成的矩陣消成單位矩陣,這樣最后消出來的方程就是
a11*x=b1
a22*y=b2
.....
ann*k=bn
所有的a和b都是消完后的數
#include<iostream> #include<cstdio> #include<cmath> using namespace std; int n,mo=100000007; double a[1001][1001]; int main() { bool sc=0; scanf("%d",&n); for(int i=1;i<=n;i++)//讀入沒什么好說的 for(int j=1;j<=n+1;j++) scanf("%lf",&a[i][j]); int bj,flag=0; for(int i=1;i<=n;i++) {bj=i; while(a[bj][i]==0&&bj<=n) bj+=1; if(bj==n+1){ flag=1;continue;//如果有一列全0,那么它就沒有價值了 } for(int j=1;j<=n+1;j++)swap(a[i][j],a[bj][j]);//交換,使第一行第一列的數不為0 double kk=a[i][i];//確保對角線上的數是1 for(int j=1;j<=n+1;j++) {a[i][j]/=kk;//其余數跟着處理 }
//接下來這一坨可能會比較難理解,在下面再講一下
for(int j=1;j<=n;j++)//這一坨就是把經過處理對角線后的矩陣消成純正的單位矩陣(就是除了對角線以外的數都清0) {if(i!=j){double k=a[j][i];//k=這行第一個數 for(int m=1;m<=n+1;m++) {a[j][m]-=k*a[i][m];//此行所有的數減去第一個數*k,使第一個數變0,當后面再處理時因為上個數已經是0了,所以沒有影響 } } } if(i==n)//消完輸出 {for(int j=1;j<=n;j++) {for(int m=1;m<=n+1;m++) cout<<a[j][m]<<" "; cout<<endl; sc=1; } } } if(!sc)printf("False");//sc就是輸出啦,如果無輸出,就代表無解 }
補鍋:
上面一坨東西只保證了對角線是1,但其他數還是一撮魑魅魍魎,我們要把這些魑魅魍魎都消成0.
可能用圖來表示要好一些
這貨是對角線被處理過的玩意-------------->j從1開始,到n,模擬1到n行,同時,列用k代替。
k每跑完1次,j就+1,即進行下一行。但i是不變的,這樣就是以i為“主體”,對i后面的每一行進行操作,使其變為一個更小的方陣。(因為每跑完一遍這一坨,第i列以前就被消成0,除了對角線,那它就消好了,后面就不用管了)這樣對角線確實會變,但下一輪循環i的時候對角線會被維護,且之前消成0的數不會被改變
其思路類似下圖
第一遍 第二遍(消陰影部分)
第三遍
其實就是把大矩陣消成小矩陣,一直到消完(白色部分是一定不會變化的數,也就是消好的對角線和0)
補鍋*2:
很多人問flag是干嘛用的,那是用來判斷是無解還是有無窮多解的東西,不過一時懶腦細胞耗盡沒有寫上
如何判斷是有無窮解還是無解呢?
既然我們有了flag=1(系數有一行全是0),那么這個方程組不是無解就是無窮多解
什么情況下會無解?
那就是有一行系數全是0,但是方程結果不是0,就會無解。
這樣我們就可以把消完的矩陣全判一遍,如果都不是無解,就是有無窮多解了
完整版代碼如下:
#include<iostream> #include<cstdio> #include<cmath> using namespace std; int n,mo=100000007; double a[1001][1001]; long double wucha=1e-9; bool pd(int h) { if(a[h][n+1]<wucha)return 1;//這里double類型的數會有一個誤差,若這個數等於0,就是小於這個誤差 for(int i=1;i<=n;i++) {if(a[h][i]>wucha)return 1; } return 0; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++)//讀入沒什么好說的 for(int j=1;j<=n+1;j++) scanf("%lf",&a[i][j]); int bj,flag=0; for(int i=1;i<=n;i++) {bj=i; while(a[bj][i]==0&&bj<=n) bj+=1; if(bj==n+1){ flag=1;continue;//如果有一列全0,那么它就沒有價值了 } for(int j=1;j<=n+1;j++)swap(a[i][j],a[bj][j]);//交換,使第一行第一列的數不為0 double kk=a[i][i];//確保對角線上的數是1 for(int j=1;j<=n+1;j++) {a[i][j]/=kk;//其余數跟着處理 } for(int j=1;j<=n;j++)//這一坨就是把經過處理對角線后的矩陣消成純正的單位矩陣(就是除了對角線以外的數都清0) {if(i!=j){double k=a[j][i];//k=這行第一個數 for(int m=1;m<=n+1;m++) {a[j][m]-=k*a[i][m];//此行所有的數減去第一個數*k,使第一個數變0,當后面再處理時因為上個數已經是0了,所以沒有影響 } } } if(i==n)//消完輸出 {for(int j=1;j<=n;j++) {for(int m=1;m<=n+1;m++) cout<<a[j][m]<<" "; cout<<endl; } } } if(flag) {for(int i=1;i<=n;i++) if(!pd(i)){printf("No Solution!");return 0; } printf("Many Many Solutions!");//瞎寫的不要在意那么多細節(比如語法什么的) } }
吐血三升后肝出來的高消
高斯消消樂真好玩QwQ
諸位大佬們有覺得注釋不詳細的地方說出來好不好