天天看点

线性代数——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