超長篇慎入!!!
線性代數
鎮樓圖

Pixiv:csyday
友情提醒
代碼并非一下子寫完的,寫的時候會有bug(大多數問題都是記憶體),雖然已經修複了但也可能還存在着一些bug和我沒考慮到記憶體洩漏的地方
〇、準備工作(建構分數ADT)
要知道網上很多線上計算最煩的一點是沒有分數全都是小數,這樣答案也寫不進作業中。
為了友善學習寫作業,這裡特地用程式實作
是以解決線代的第一步應該是建構一個分數來避免這種問題
typedef struct __fraction{
int numerator;//分子
int denominator;//分母
}fraction,*Fraction;
當然目前的這個分數存在一定問題,我設定的分子和分母都是整數,如果碰到了根号、對數、指數這種就沒轍了。不過個人認為這樣也已經能解決相當多的問題了
然後寫一些基本操作
分數操作1:判斷是否有效
bool fraction_ifexist(Fraction a){
//作用:判斷分數是否是有效的
//如果分母為0則傳回0視為無效
//如果分母不為0則傳回1視為有效
return (a->denominator == 0) ? false : true;
};
分數操作2:判斷是否為真分數
int fraction_ifproper(Fraction a){
//作用:判斷分數是否為真分數
//如果分數是真分數則傳回1否則傳回0
if(!fraction_ifexist(a)){
printf("錯誤:分母為0!!!");
exit(-1);
}
return (a->denominator > a->numerator) ? true : false;
};
分數操作3:約分
int gcd(int a,int b){
//求最大公約數
b = abs(b);
a = abs(a);
if(b > a){
int t = a;
a = b;
b = t;
}
return (b == 0) ? a : gcd(b,a%b);
}
void fraction_simplify(Fraction a){
//作用:約分
if(!fraction_ifexist(a)){
printf("錯誤:分母為0!!!");
exit(-1);
}
int GCD = gcd(a->numerator,a->denominator);
a->numerator /= GCD;
a->denominator /= GCD;
if(a->denominator < 0){
a->numerator *= -1;
a->denominator *= -1;
}
return;
}
分數操作4:通分
int lcm(int a,int b){
//求最小公倍數
return abs(a)*abs(b)/gcd(a,b);
}
void fraction_complicated(Fraction a,Fraction b){
//作用:通分
if(!fraction_ifexist(a) || !fraction_ifexist(b)){
printf("錯誤:分母為0!!!");
exit(-1);
}
int LCM = lcm(a->denominator,b->denominator);
a->numerator *= LCM/a->denominator;
b->numerator *= LCM/b->denominator;
a->denominator = b->denominator = LCM;
return;
}
分數操作5:加減
Fraction fraction_add(Fraction a,Fraction b){
//作用:加法
if(!fraction_ifexist(a) || !fraction_ifexist(b)){
printf("錯誤:分母為0!!!");
exit(-1);
}
Fraction r = (Fraction)malloc(sizeof(fraction));
fraction_complicated(a,b);
r->numerator = a->numerator + b->numerator;
r->denominator = a->denominator;
fraction_simplify(r);
return r;
}
Fraction fraction_sub(Fraction a,Fraction b){
//作用:減法
if(!fraction_ifexist(a) || !fraction_ifexist(b)){
printf("錯誤:分母為0!!!");
exit(-1);
}
Fraction r = (Fraction)malloc(sizeof(fraction));
fraction_complicated(a,b);
r->numerator = a->numerator - b->numerator;
r->denominator = a->denominator;
fraction_simplify(r);
return r;
}
分數操作6:乘除
Fraction fraction_mul(Fraction a,Fraction b){
//作用:乘法
if(!fraction_ifexist(a) || !fraction_ifexist(b)){
printf("錯誤:分母為0!!!");
exit(-1);
}
Fraction r = (Fraction)malloc(sizeof(fraction));
r->numerator = a->numerator * b->numerator;
r->denominator = a->denominator * b->denominator;
fraction_simplify(r);
return r;
}
Fraction fraction_div(Fraction a,Fraction b){
//作用:除法
if(!fraction_ifexist(a) || !fraction_ifexist(b)){
printf("錯誤:分母為0!!!");
exit(-1);
}
Fraction r = (Fraction)malloc(sizeof(fraction));
r->numerator = a->numerator * b->denominator;
r->denominator = a->denominator * b->numerator;
fraction_simplify(r);
return r;
}
分數操作7:輸出
void fraction_print(Fraction a){
//作用:約分并輸出
if(!fraction_ifexist(a)){
printf("錯誤:分母為0!!!");
exit(-1);
}
fraction_simplify(a);
if(a->denominator == 1 || a->numerator == 0){
a->denominator = 1;
printf("%d",a->numerator);
}else printf("%d/%d",a->numerator,a->denominator);
return;
}
分數操作8:生成分數
當然需要一個能夠生成分數的函數,而不是自己手動去設定一個分數變量再填資料
fraction fraction_create(int a,int b){
//生成一個以a為分子b為分母的分數
fraction r;
r.numerator = a;
r.denominator = b;
return r;
}
這樣就能成功建構了分數的ADT,當然這是最基礎,然後我們依據線代逐漸寫出一個可計算的DLL
代碼看不懂無所謂,隻要能用就行,矩陣的代碼才是核心
/*類型:fraction、*Fraction
①fraction_ifexist(Fraction a)
判斷分數a是否有效
②fraction_ifproper(Fraction a)
判斷分數a是否為真分數
③fraction_simplify(Fraction a)
将分數a化簡為既約分數
④fraction_complicated(Fraction a,Fraction b)
将分數a和分數b通分
⑤fraction_add(Fraction a,Fraction b)
fraction_sub(Fraction a,Fraction b)
兩分數相加、相減
⑥fraction_mul(Fraction a,Fraction b)
fraction_div(Fraction a,Fraction b)
兩分數相乘、相除
⑦fraction_print(Fraction a)
按照一定格式輸出分數a
⑧fraction_create(int a,int b)
生成a為分子,b為分母的分數
一、線性方程(組)
線性方程
\(a_1x_1+a_2x_2...+a_nx_n=b\)
其中\(a_i\)為系數,\(x_i\)為變量,\(b\)為值
\(a_i\)和\(b\)可以為實數或複數
解
得出的一組可行的變量值組合為解
解分為三種情況
解構成的集合稱為
解集
有解的線性方程(組)稱為是
相容
的
無解的線性方程(組)稱為是
不相容
線性方程組
所謂線性方程組是一組線性方程構成的一組變量有關聯的方程組
比如
\(\begin{cases}&x&+&y&+&z=&50 \\ &5x&+&2y&+&z=&100 \end{cases}\)
增廣矩陣
在解方程時如果原本變量位置不變可以發現,變量并不影響我們計算
是以可以将變量、運算符去掉,隻保留系數、值
這樣由線性方程組用矩陣符号化後的矩陣稱為'增廣矩陣'
\(\begin{bmatrix}1&1&1&50 \\ 5&2&1&100 \end{bmatrix}\)
這個矩陣中\(\begin{bmatrix}1&1&1&50\end{bmatrix}\)稱為
行
這個矩陣中\(\begin{bmatrix}1 \\ 50\end{bmatrix}\)稱為
列
維數
是指矩陣所包含的行數和列數
建構矩陣ADT
現在來嘗試用計算機存儲矩陣,首先資料元素是剛才建構的分數。
這裡我采用二維數組表示
typedef struct __matrix{
Fraction **base;//矩陣首元素
int row;//矩陣行數
int col;//矩陣列數
}matrix,*Matrix;
矩陣操作1:建立零矩陣
所謂零矩陣即矩陣内所有元素均為0的矩陣
void matrix_null(Matrix a,int m,int n){
//建立m×n的零矩陣
a->base = (Fraction**)malloc(sizeof(Fraction*)*m);
for(int i = 0;i < m;i++)
a->base[i] = (Fraction*)malloc(sizeof(Fraction)*n);
//二維數組賦予記憶體稍麻煩
a->row = m;
a->col = n;
for(int i=0;i < a->row;i++)
for(int j=0;j < a->col;j++)
a->base[i][j] = fraction_create(0,1);
return;
}
矩陣操作2:删除某一進制素
void matrix_edelete(matrix a,int i,int j){
//釋放矩陣内a(i,j)的記憶體
free(a.base[i][j]);
return;
}
矩陣操作2:設定某一位置的元素
void matrix_set(matrix a,int i,int j,Fraction value){
//将矩陣a的第i、j位置的元素修改為value
matrix_edelete(a,i,j);
a.base[i][j] = value;
return;
}
矩陣操作3:輸出矩陣
void matrix_print(matrix a){
//輸出矩陣
for(int i = 0;i < a.row;i++){
for(int j = 0;j < a.col;j++){
fraction_print(a.base[i][j]);
printf("\t");
}
printf("\n");
}
}
矩陣操作4:删除矩陣
void matrix_delete(matrix a){
//删除矩陣
for(int i = 0;i < a.row;i++){
free(a.base[i]);
}
free(a.base);
a.base = NULL;
a.col = a.row = 0;
return;
}
現在我們就能用幾行代碼來生成一個矩陣了,更加具體的操作我會随着線代的學習來寫代碼
行初等變換(僅限增廣矩陣)
增廣矩陣不過是符号化後的線性方程組,線性方程組該怎麼求還就怎麼求
根據原本對線性方程組的求解方法,在增廣矩陣裡可以抽象為一種規律
使用下述規律後這個增廣矩陣依然等價
①倍加:可以将某一行加上另一行的某個倍數
②對換:可以任意交換兩行
③倍乘:某一行可以乘上某一不為0的倍數
\(\begin{bmatrix}1&1&1&50 \\ 5&2&1&100 \end{bmatrix} \approx \begin{bmatrix}1&1&1&50 \\ 4&1&0&50 \end{bmatrix} \approx \begin{bmatrix}4&1&0&50 \\ 1&1&1&50 \end{bmatrix}\)
求解線性方程組時有兩個最基本的問題
1.線性方程組是否相容?
2.解是否唯一?
比如求解方程組
\(\begin{cases}x&-&2y&+&z&=&0 \\0&+&2y&-&8z&=&8 \\ -4x&+&5y&+&9z&=&-9 \end{cases}\)
然後用增廣矩陣做點處理得到
\(\begin{bmatrix}2&-3&2&1 \\ 0&1&-4&4 \\ 0&0&0&\frac{5}{2} \end{bmatrix}\)
最後一行顯然是沖突的,是以此方程組是不相容的
矩陣操作5:判斷某一行是否超出目前矩陣行數
bool matrix_ifrow(matrix a,int i){
//判斷第i行是否在a矩陣裡合法
return (i >=0 && i < a.row) ? true : false;
}
矩陣操作5:行初等變換
現在來從代碼上實作行初等變換
①倍加
倍加就算了,會讓參數變多,就加法即可
void matrix_transfrom_radd(matrix a,int i1,int i2){
//矩陣a進行初等行變換,i1行加i2行結果于i1行
if(!matrix_ifrow(a,i1) || !matrix_ifrow(a,i2)){
printf("錯誤:所描述的行已超出行數");
exit(-1);
}
for(int j = 0;j < a.col;j++){
a.base[i1][j] = fraction_add(a.base[i1][j],a.base[i2][j]);
}
return;
}
②對換
void matrix_transfrom_rreplace(matrix a,int i1,int i2){
//矩陣a進行初等行變換,i1行與i2行交換
if(!matrix_ifrow(a,i1) || !matrix_ifrow(a,i2)){
printf("錯誤:所描述的行已超出行數");
exit(-1);
}
for(int j = 0;j < a.col;j++){
Fraction t = a.base[i1][j];
a.base[i1][j] = a.base[i2][j];
a.base[i2][j] = t;
}
return;
}
③倍乘
void matrix_transfrom_rmul(matrix a,int i,Fraction c){
//矩陣a進行初等行變換,i1行乘上某已分數
if(!matrix_ifrow(a,i)){
printf("錯誤:所描述的行已超出行數");
exit(-1);
}
for(int j = 0;j < a.col;j++){
a.base[i][j] = fraction_mul(a.base[i][j],c);
}
return;
}
行階梯形矩陣
根據最基礎的行初等變換可以求解線性方程組,但要如何變換,或者說要變換到什麼樣的矩陣才是我們想要的。
\(\begin{bmatrix}1&0&0&15 \\ 0&1&0&16 \\ 0&0&1&17 \end{bmatrix}\) \(\begin{bmatrix}1&2&3&15 \\ 0&4&5&16 \\ 0&0&6&17 \end{bmatrix}\)
像這兩類矩陣能特别快速地求解,像這種能夠快速求解的矩陣稱為行階梯形矩陣
排除0元素我們能感覺這個矩陣像一個階梯一樣
以下面這個矩陣為例
\(\begin{bmatrix}0&1&2&3&0&15 \\ 0&0&5&6&7&16 \\ 0&0&0&0&9&17 \\ 0&0&0&0&0&0 \end{bmatrix}\)
其中矩陣當中如果有某一行元素全部為0就稱這是
零行
在非零行的最左側的元素稱為
先導元素
,這裡是1,5,9
為了符号化的表示,用
■
表示先導元素,
*
表示由先導元素所形成的階梯内的元素,其他元素為0的則省略不寫
此例符号化表示為
\(\begin{bmatrix}&■&*&*&*&* \\ &&■&*&*&* \\ &&&&■&* \\ &&&&& \end{bmatrix}\)
行階梯形矩陣定義
隻有滿足以下條件才能算得上是行階梯矩陣
①非零行要在零行上
②先導元素一定是從左到右依次排列好的
③先導元素所在列的下方元素都為0
簡化行階梯形矩陣(U)
這是增廣矩陣的最簡形式,簡單到能一眼看出解,這是比其他行階梯形矩陣還要好算的形式
除了行階梯形矩陣的定義以外還要滿足以下條件
①先導元素為1