天天看點

線性代數——C語言實作(超長篇)【9/2目前完成10%】

超長篇慎入!!!

線性代數

鎮樓圖

線性代數——C語言實作(超長篇)【9/2目前完成10%】

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;
}
           

現在我們就能用幾行代碼來生成一個矩陣了,更加具體的操作我會随着線代的學習來寫代碼

線性代數——C語言實作(超長篇)【9/2目前完成10%】

行初等變換(僅限增廣矩陣)

增廣矩陣不過是符号化後的線性方程組,線性方程組該怎麼求還就怎麼求

根據原本對線性方程組的求解方法,在增廣矩陣裡可以抽象為一種規律

使用下述規律後這個增廣矩陣依然等價

①倍加:可以将某一行加上另一行的某個倍數

②對換:可以任意交換兩行

③倍乘:某一行可以乘上某一不為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