(一)需求和规格说明
输入是 N(N<256) 元线性方程组 Ax=B, 输出是方程组的解,也可能无解或有多组解。可以用高斯消去法求解,也可以采用其它方法。
要求给出线性方程组的矩阵,能够输出线性方程组的解。
(二)设计
根据上述需求,对于解线性方程组,一般选择高斯消元法。
Gauss消元法的基本做法就是把方程组转化成为一个如下图的等价的三角方程组,这个过程叫做消元。得到三角方程组后,就可以逐个求出Xn,Xn-1,…,X1,这个过程叫回代。
对于高斯消元法解线性方程组。由于线性方程组系数矩阵及其增广矩阵经过初等行变换所得到的矩阵对应的方程与原方程同解,所以首先需要写出线性方程组的增广矩阵,对增广矩阵进初等行变换将其转换成阶梯型矩阵,然后需要调用一个函数来求其秩(有解时)及其无解情况。
其基本模块分为三大部分。第一部分为输入矩阵阶段,用for语句实现。第二部分是对矩阵进行一系列的处理以求得线性方程组的解,先运用初等行变换化为阶梯型,如果不符合所需形式则再次进行初等列变,然后输出化简矩阵;然后以线性方程组的秩判断其是否有解(规定无解时秩为零)。第三部分是输出线性方程组的解情况及其解,如果无解即输出无解;如果有单解,则输出单解的情况;如果有无穷解,输出其一般解的情况。
程序运行的顺序是:
(1)输入增广矩阵后,运用初等行变换,使其a[i][i]以下全为零,若a[i][i]为零,运用行变换和列变换交换使其不为零。
(2)输出阶梯型矩阵。
(3)判断解情况并输出(解情况)
(4)如果存在解则输出解
本程序一共设计了四个函数,分别是:
void transformation(int, int, double**);
//初等行变换函数
void Judge(int, int, double**);
//判断a[i][i]是否都为非0,否则进行列变换
int r(int, int, double**);
//求秩函数
void Single(int, double**, double*);
//方程回代求解函数
(三) 用户手册
程序运行时,首先提示输入方程的个数以及未知数的个数。
然后提示输入线性方程组的增广矩阵(双精度浮点型数据)。
接着程序调用初等行变换函数并输出变换后的阶梯型矩阵;
同时调用求秩函数和求单解函数得到函数解的情况。
如果无解,则输出该线性方程组无解;
如果有单解,则输出该线性方程组的单解;
如果有无穷多解,则输出该线性方程则的一般解并输出自由未知量。
(四) 调试及测试
在调试过程中,发现在if语句定义的变量所开辟的空间在if语句外释放发生报错,通过逐行运行代码,发现在if语句定义的变量作用域只在if语句里面,故应在if语句里释放空间。
在求方程的秩时,为减少代码的复杂度以及运行的时间,只需要发现每行存在一个非0系数即可中止for循环,故设置一个iflag,一旦检测到有非0系数,存在,则跳出循环,同时iflag值改变,也为判断系数全为0而非齐次项不为0的情况提供了便利。
同时,在输入不同的数据时发现程序仍然存在不完善的地方,比如当初等行变换后,存在a[i][i]仍然为0的情况,通过对高等代数进一步的学习,设计了一个初等列变换函数,保证a[i][i]不为0。
在最后的完善阶段,又发现一个问题,在输出一般解的时候,第一项前的符号问题,若为自由未知变量且其前系数为正时,不需要额外添加“+”号,而从第二项开始,当出现自由未知变量且其前系数为正时,需要添加“+”号,故在仔细思考后,加入一个iflag标志变量,初始值为0,将其添加入输出首项的if语句内,如果第一项是自由未知变量且其前系数为正时,无需添加符号,当第一项输出后,iflag变为1,则后面若是输出自由未知变量且其前系数为正时,需添加“+”号。
(五) 运行实例:
①
请输入方程的个数 m = 4
请输入未知数个数 n = 4
请输入对应方程的增广矩阵 :
2 -1 3 2 6
3 -3 3 2 5
3 -1 -1 2 3
3 -1 1 -1 2
初等行变化后的阶梯型矩阵如下 :
2 -1 3 2 6
0 -1.5 -1.5 -1 -4
0 0 -6 -1.33333 -7.33333
0 0 0 -3.44444 -3.44444
该线性方程组有唯一解!
该方程组的解如下 :
X(1) = 1
X(2) = 1
X(3) = 1
X(4) = 1
②
请输入方程的个数 m = 5
请输入未知数个数 n = 6
请输入对应方程的增广矩阵 :
1 0 0 1 0 0 40
0 1 0 0 1 0 20
0 0 1 0 0 1 10
1 1 1 0 0 0 45
0 0 0 1 1 1 25
初等行变化后的阶梯型矩阵如下 :
1 0 0 1 0 0 40
0 1 0 0 1 0 20
0 0 1 0 0 1 10
0 0 0 -1 -1 -1 -25
0 0 0 0 0 0 0
该线性方程组有无穷多解!
该线性方程组的通解如下 :
X(1) = 15 + 1*X(5) + 1*X(6)
X(2) = 20-1*X(5)
X(3) = 10-1*X(6)
X(4) = 25-1*X(5)-1*X(6)
X(5) = X(5)
X(6) = X(6)
其中,自由未知量是 :X(5) X(6)
③
请输入方程的个数 m = 3
请输入未知数个数 n = 2
请输入对应方程的增广矩阵 :
1 2 5
1 1 4
2 1 3
初等行变化后的阶梯型矩阵如下 :
1 2 5
0 -1 -1
0 0 -4
该线性方程组无解!
(六)进一步改进
(1)运用高斯消元法在求线性方程组的时候,若存在一些数据相除后为无限不循环小数,最终结果精度上可能存在问题,需要在日后学习的算法过程中不断完善自己的知识储备,来解决这类问题。
(2)编码过程中一些编码格式不符合C++编码规范,每一种编程语言都有自己的编码规范, 严格遵守其编码规范有助于代码的可读性,减少代码的二义性增强代码的可移植性。在该软件的源代码中存在大量编码规范问题,从而可能导致后期维护成本增大。应该修改代码使之符合编码规范。
(3)高等代数中与矩阵相关的算法存在很多,比如高斯消元法求矩阵的逆,伴随矩阵等,在今后对高等代数的深入学习,希望也能通过代码实现此类题目。
(七)心得体会
对于本题,采用高斯列主元消去法解线性方程组得到的结果和实际的计算结果是一致的, 高斯列主元消去法比较简单, 在提高近似值解的精度上是非常起作用的, 而且又具有计算量不大、算法组织容易, 且对于有唯一解的线性方程组都可以计算,要求的条件比较低等等优点。
不过, 由于消去的循环次数比较多, 当要解决比较大的线性方程组时计算就比较慢了。 但对于一般的方程组, 高斯列主元消去法还是能够胜任的。 高斯消去法是常用的解线性方程组的基本方法。
在完成课设的过程中,也收获了不少心得感悟。首先,识别对象,明确对象的功能,在解决问题过程中是基本的。在此次研究的问题中,对象就是线性方程组以及高斯消元法,只有明确了高斯消元法的功能原理,以及线性方程组的基本性质,才能进行程序的基本设想与编写。
其次对象的方法就是一个函数,与普通函数的抽象区别不大,同样是一个IPO,需要限定其功能,给一个能反映其功能的名称,确定它需要的数据、返回的数据。对对象的方法而言,除了调用它的对象自身的属性数据外,完成功能需要的数据必须通过参数进行传递。
最后,我更加更加深刻的理解了高斯消元法解线性方程组的思路与原理,对编程软件的运用更加纯熟。自己完成课设的过程中会遇到各种各样的问题,在不断解决问题的过程中,自己的知识储备也在不断扩大,在最终完成课设的时候,成就感和兴奋感正是对辛勤奋斗的自己的一种最好的回报!
(八)对课程设计的建议
课程设计对我们的综合编程能力有着极大的考验,非常锻炼我们的编程思维和逻辑推理能力,这对我们巩固编程知识和提升编程能力十分重要。
希望课程设计能够多提供一些切合我们生活实际,符合当今时代的更加新颖的题目,培养我们运用编程解决实际问题的能力。
(九)附录——源程序
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <iomanip>
using namespace std;
void transformation(int, int, double**); //初等行变换函数
void Judge(int, int, double**); //判断a[i][i]是否都为非0,否则进行列变换
int r(int, int, double**); //求秩函数
void Single(int, double**, double*); //方程回代求解函数
int main()
{
int m, n;
cout << "请输入方程的个数 m = ";
cin >> m;
cout << "请输入未知数个数 n = ";
cin >> n;
double** a = new double* [m]; //创建动态二维数组来存放矩阵
for (int i = 0; i < m; i++)
{
a[i] = new double[n + 1];
}
double* x = new double[n]; //存放方程的解
cout << endl << "请输入对应方程的增广矩阵 :" << endl << endl;
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n + 1; j++)
{
cin >> a[i][j];
}
}
transformation(m, n, a);
Judge(m, n, a);
//此时已化为阶梯形矩阵,下面求秩,调用一个函数进行列交换,求秩
int ra = r(m, n, a);
cout << endl << "初等行变化后的阶梯型矩阵如下 : " << endl << endl;
for (int i = 0; i < m; i++)
{
for (int j = 0; j <= n; j++)
{
cout << setiosflags(ios::left) << setw(10) << a[i][j];
}
cout << endl; //输出最终的阶梯型矩形。
}
if (ra == 0) //无解
{
cout << endl << "该线性方程组无解!" << endl;
}
else if (ra == n) //有唯一解,进行求解并输出
{
cout << endl << "该线性方程组有唯一解!" << endl;
Single(n, a, x);
cout << "该方程组的解如下 :" << endl;
for (int i = 0; i < n; i++)
{
cout << "X(" << i+1 << ") = " << x[i] << endl;
}
}
else if (ra < n && ra != 0) //无穷多解
{
cout << endl << "该线性方程组有无穷多解!" << endl;
double** b = new double* [ra]; //创建动态二维数组
for (int i = 0; i < ra; i++)
{
b[i] = new double[n-ra+1];
}
double** xx = new double* [ra]; //创建动态二维数组
for (int i = 0; i < ra; i++)
{
xx[i] = new double[n-ra+1];
}
for (int i = 0; i < ra; i++)
{
for (int k = 0; k < n - ra + 1; k++)
{
b[i][k] = a[i][k+ra];
}
} //二维数组b存放阶梯型矩阵每行最后n-ra+1个数,最后一行正好剩第一个。
for (int i = 0; i < ra; i++)
{
a[i][ra] = b[i][n-ra]; //将阶梯型数组中每行第ra+1个元素用第n-ra+1个元素替换
} //最后一行第二个和常数项
Single(ra, a, x);
for (int i = 0; i < ra; i++)
{
xx[i][0] = x[i]; //每个主变量中的常数项
}
for (int j = 1; j <= n - ra; j++)
{
for (int i = 0; i < ra; i++)
{
a[i][ra] = 0;
a[i][ra] = a[i][ra] - b[i][j-1];
}
Single(ra, a, x);
for (int i = 0; i < ra; i++)
{
xx[i][j] = x[i]; //求出每个主变量中自由未知量前面的系数并存放到二维数组xx中
}
}
cout << "该线性方程组的通解如下 : " << endl;
for (int i = 0; i < n; i++)
{
int iflag = 0; //iflag来判断是否为第一项,0则为第一个数据,1则反
if (i < ra)
{
cout << "X(" << (i + 1) << ") = ";
for (int j = 0; j <= n - ra; j++)
{
if (j == 0)
{
if (xx[i][j])
{
cout << xx[i][j]; //输出主变量的常数项
iflag = 1;
}
}
else
{
if (xx[i][j] > 0 && iflag == 0) //若此前都为0,第一项输出时若为正,则无需“+”
{
iflag = 1;
cout << xx[i][j] << "*X(" << j+ra << ")";
}
else if (xx[i][j] > 0 && iflag == 1) //第一项往后若存在正数项,都需“+”号
{
cout << " + " << xx[i][j] << "*X(" << j+ra << ")";
}
else if (xx[i][j] < 0 ) //负数输出无需符号
{
iflag = 1;
cout << xx[i][j] << "*X(" << j+ra << ")"; //若第一项为“-",则无需添加符号,同时使iflag=1
}
}
}
}
else
{
cout << "X(" << (i + 1) << ") = " << "X(" << (i + 1) << ")"; //自由位置量的输出
}
cout << endl;
}
cout <<endl<< "其中,自由未知量是 : "; //自由未知量的注明
for (int i = ra; i < n; i++)
{
cout << "X(" << (i + 1) << ") ";
}
cout << endl;
for (int i = 0; i < ra; i++) //开辟的二维数组的释放
{
delete[] b[i];
}
delete[] b;
for (int i = 0; i < ra; i++)
{
delete[] xx[i];
}
delete[] xx;
}
for (int i = 0; i < m; i++)
{
delete[] a[i];
}
delete[] a;
delete[] x;
system("pause");
return 0;
}
void transformation(int m, int n, double** a)
{
double m1, m2;
int b = 0;
for (int i = 0; i < n && i < m; i++)
{
if (a[i][i] != 0)
{
m1 = a[i][i]; //选取a[i][i]进行操作
for (int j = i + 1; j < m; j++)
{
m2 = a[j][i]; //选取a[i][i]所在第i列的以下元素
for (int k = 0; k <= n; k++)
{
a[j][k] = a[j][k] - a[i][k] * m2 / m1; //采用倍数消元法依次对每行进行初等行变换
}
}
} //初等行变换,使其a[i][i]以下全为零,得到阶梯型矩阵
else if (a[i][i] == 0)
{
for (int j = i + 1; j < m; j++)
{
if (a[j][i] != 0)
{
for (int k = 0; k <= n; k++) //交换i,j两行使a[i][i]!=0
{
b = a[i][k];
a[i][k] = a[j][k];
a[j][k] = b;
}
break;
}
}
if (a[i][i] != 0)
{
m1 = a[i][i]; //交换后再进行初等行变换
for (int j = i + 1; j <= m - 1; j++)
{
m2 = a[j][i];
for (int k = 0; k <= n; k++)
{
a[j][k] = a[j][k] - a[i][k] * m2 / m1;
}
}
}
}
}
}
void Judge(int m, int n, double** c)
{
double temp = 0.0;
double m1 = 0.0, m2 = 0.0;
for (int i = 0; i < m - 1; i++)
{
if (c[i][i] == 0) //如果化为阶梯型矩阵后a[i][i]仍为0,交换列
{
for (int j = i + 1; j < n; j++)
{
if (c[i][j] != 0)
{
for (int k = i; k < m; k++) //交换i,j两列使a[i][i]!=0
{
temp = c[k][j];
c[k][j] = c[k][i];
c[k][i] = temp;
}
break;
}
}
for (int j = i + 1; j < m; j++)
{
m1 = c[i][i];
m2 = c[j][i];
for (int k = i + 1; k <= n; k++)
{
c[j][k] = c[j][k] * m1 - c[i][k] * m2;
}
}
}
}
}
int r(int m, int n, double** c) //求阶梯型方程组的秩
{
int r = 0, iFlag = 0;
for (int i = 0; i < m; i++)
{
iFlag = 0;
for (int j = 0; j < n; j++)
{
if (c[i][j] != 0)
{
iFlag = 1;
r++;
break; //若存在非0系数,则秩加一,同时跳出循环且使iFlag变成1。
}
}
if (iFlag == 0 && c[i][n] != 0) //若存在系数全为0且非齐次项不为0,则原方程无解,直接结束程序。
{
return 0;
}
}
return r; //返回方程组的秩
}
void Single(int n, double** a, double* x)
{
int i, j;
for (i = n - 1; i >= 0; i--)
{
if (i == n - 1)
{
x[i] = a[i][n] / a[i][i]; //求出最下面的一元一次方程组的解
}
else
for (j = n - 1; j > i; j--)
{
a[i][n] = a[i][n] - x[j] * a[i][j]; //往上回代用代入消元法
}
x[i] = a[i][n] / a[i][i]; //分别回代方程组依次求得各解
}
}
运行实例:(5*6矩阵无穷解的情况)
初学C++的小白的第一篇文章,素材是刚刚完成的课程设计报告。
算法的实现参考了网络上的许多大牛,在此基础上加入自己的想法并添加了详细的注释。
如有不当的地方,还望大佬们多多指正!
附:
学习线性方程组时参考的资料(来自丘维声老师的高等代数)