超长篇慎入!!!
线性代数
镇楼图

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