实现求解线性方程(矩阵、高斯消去法)------c++程序设计原理与实践(进阶篇)
步骤:
其中A是一个n*n的系数方阵 向量x和b分别是未知数和常量向量:

这个系统可能有0个、1个或者无穷多个解,这取决于系数矩阵A和向量b。求解线性系统的方法有很多,这里使用一种经典的方法——高斯消去法(https://zh.wikipedia.org/wiki/高斯消去法)。首先,我们对A和b进行交换,使得A变为一个上三角矩阵。上三角矩阵就是对角线之下的所有元素均为0。即如下形式:

实现这个目标是很容易的。为了使a(i,j)变为0,我们先将它乘以一个常量,使它等于第j列上的另一个元素,比如说等于a(k,j)。然后,用第i个方程减去第k个方程,a(i,j)即变为0,矩阵第i行其他元素的值也相应发生改变。
如果这样一个变换最终使得对角线上所有元素都非0,方程组就有唯一解,此解可以通过”回代“求得。如果存在为0的元素,则意味着方程组有0个或者无穷多个解。
我们现在用c++来表示上述计算方法。首先,定义两个要使用的具体Matrix类型,以简化程序:
typedef Numeric_lib::Matrix<double, 2>Matrix2; // Matrix库下载地址 :http://www.stroustrup.com/Programming/Matrix/Matrix.h 整个库定义在名字空间 Numeric_lib 中
typedef Numeric_lib::Matrix<double, 1> Vector;
接下来我们将高斯消去法计算过程描述为程序:
Vector classic_gaussian_elimination(Matrix2 A,Vector b){
classical_elimination(A,b);
return back_substitution(A,b);
}
即,先为两个输入A和b创建拷贝(使用赋值函数),然后调用一个函数求解方程组,最后调用回代函数计算结果并将结果返回。关键之处在于,我们分解问题的方式和符号表示都完全来自于原始的数学描述。下面所要做的就是实现classic_elimination()和back_substitution()了,解决方案同意完全来自于数学教科书:
void classical_elimination(Matrix2&A,Vector& b){
const Index n=A.dim1();
//从第1列一直遍历到倒数第二列
//将对角线之下所以元素都填充0
for(Index j=0;j<n-1;++j){
const double pivot =A(j,j);
if(pivot==0)cerr<<"错误:其中有一对角线位为0"<<'\n';
//将第i行在对角线之下的元素都填充为0
for(Index i=j+1;i<n;++i){
const double mult =A(i,j)/pivot;
A[i].slice(j)=scale_and_add(A[j].slice(j),-mult,A[i].slice(j)); //A[i].slice(j)表示从A[i][j]到这一行的末尾。
b(i)-=mult*b(j); //对b做对应变化
}
}
}
“pivot”表示当前行位于对角线上的元素,它必须是非0。因为需要用它作为除数;如果它为0,我们将放弃计算,抛出一个异常:
Vector back_substitution(const Matrix2&A,const Vector&b){
const Index n=A.dim1();
Vector x(n);
for(Index i=n-1;i>=0;--i){
double s=b(i)-dot_product(A[i].slice(i+1),x.slice(i+1));
if(double m=A(i,i))
x(i)=s/m;
else
throw Back_subst_failure(i);
}
return x;
}
完整示例程序:
#include<iostream>
#include"Matrix.h" //Matrix库下载地址 :http://www.stroustrup.com/Programming/Matrix/Matrix.h
#include"MatrixIO.h" //MatrixIO库下载地址 :http://www.stroustrup.com/Programming/Matrix/MatrixIO.h 仅为一维二维提供非常简单的I/O功能
using namespace Numeric_lib; //整个库定义在名字空间 Numeric_lib 中
using namespace std;
typedef Numeric_lib::Matrix<double, 2>Matrix2;
typedef Numeric_lib::Matrix<double, 1> Vector; void classical_elimination(Matrix2& A, Vector& b) {
const Index n = A.dim1();
//从第1列一直遍历到倒数第二列
//将对角线之下所以元素都填充0
for (Index j = 0; j<n - 1; ++j) {
const double pivot = A(j, j);
if (pivot == 0)cerr<<"错误:其中有一对角线位为0"<<'\n'; //将第i行在对角线之下的元素都填充为0
for (Index i = j + 1; i<n; ++i) {
const double mult = A(i, j) / pivot;
A[i].slice(j) = scale_and_add(A[j].slice(j), -mult, A[i].slice(j));
b(i) -= mult*b(j); //对b做对应变化
}
}
}
Vector back_substitution(const Matrix2&A, const Vector&b) {
const Index n = A.dim1();
Vector x(n); for (Index i = n - 1; i >= 0; --i) {
double s = b(i) - dot_product(A[i].slice(i + 1), x.slice(i + 1)); if (double m = A(i, i))
x(i) = s / m;
else
cerr<<"错误:其中有一对角线位为0"<<'\n';
}
return x;
}
Vector classic_gaussian_elimination(Matrix2 A, Vector b) {
classical_elimination(A, b);
return back_substitution(A, b);
}
int main() {
double val2[3][3] = {2,1,-1,-3,-1,2,-2,1,2 };
double val1[3] = {8,-11,-3 };
Matrix2 A(val2);
Vector b(val1);
cout<<classic_gaussian_elimination(A, b); }
改进:
pivot为0的问题是可以避免的,我们可以对行进行排列,从而将0和较小的值从对角线上移开,这样就得到了一个更鲁棒的方案。“更鲁棒”是指对于舍入误差不敏感。但是,随着我们将0置于对角线之下,元素值也会发生改变。因此,我们必须反复进行重排序,以将较小的值从对角线上移开(即,不能一次重排矩阵后就直接使用经典算法):
void elim_with_partial_pivot(Matrix2& A, Vector& b) {
const Index n = A.dim1();
for (Index j = 0; j < n; ++j) {
Index pivot_row = j;
//查找一个合适的主元:
for (Index k = j + 1; k < n; ++k)
if (abs(A(k, j)) > abs(A(pivot_row, j))) pivot_row = k;
//如果我们找到了一个更好的主元,交换两行:
if (pivot_row != j) {
A.swap_rows(j, pivot_row);
std::swap(b(j), b(pivot_row));
}
//消去:
for (Index i = j + 1; i < n; ++i) {
const double pivot = A(j, j);
if (pivot == 0)error("can't solve:pivot==0");
const double mult = A(i, j) / pivot;
A[i].slice(j) = scale_and_add(A[j].slice(j), -mult, A[i].slice(j));
b(i) -= mult*b(j);
}
}
}
在这里我们使用了swap_rows()和scale_and_multipy(),这样程序更符合习惯,我们也不必显式编写循环代码了。
随机数测试:
void solve_random_system(Index n) {
Matrix2 A = random_matrix(n);
Vector b = random_vector(n);
cout << "A=" << A << '\n';
cout << "b=" << b << '\n';
try {
Vector x = classic_gaussian_elimination(A, b);
cout << "classic elim solution is x =" << x << '\n';
Vector v = A*x;
cout << "A*x=" << v << '\n';
}
catch (const exception& e) {
cerr << e.what() << '\n';
}
}
程序在三种情况下会进入catch语句:
- 代码中有bug。
- 输入内容使classic_elimination出现错误(elim_with_partial_pivot在很多情况下可以做得更好)。
- 舍入误差导致问题。
为了测试我们的程序,我们输出 A*x,其值应该与b相单。但考虑到存在舍入误差,若其值与b足够接近就认为结果正确,这也是为什么测试程序中没有采用下面语句来判断结果是否正确的原因:
if(A*b!=b)error ("substitution failed");
在计算机中,浮点数只是实数的近似,因此我们必须接受近似的计算结果。一般来说,应该避免使用==和!=来判断是否正确。
Matrix库中并没有定义矩阵与向量的乘法运算,因此我们为测试程序定义这个运算:
Vector operator*(const Matrix2&m, const Vector&u) {
const Index n = m.dim1();
Vector v(n);
for (Index i = 0; i < n; ++i) v(i) = dot_product(m[i], u);
return v;
}
random_matrix()和random_vector()是随机数的简单应用。Index是索引类型,它是用typedef定义的。
完整程序:
#include<iostream>
#include<random>
#include <time.h>
#include"Matrix.h" //Matrix库下载地址 :http://www.stroustrup.com/Programming/Matrix/Matrix.h
#include"MatrixIO.h"//MatrixIO库下载地址 :http://www.stroustrup.com/Programming/Matrix/MatrixIO.h
using namespace Numeric_lib;//整个库定义在名字空间 Numeric_lib 中
using namespace std;
typedef Numeric_lib::Matrix<double, 2>Matrix2;
typedef Numeric_lib::Matrix<double, 1> Vector; void classical_elimination(Matrix2& A, Vector& b) {
const Index n = A.dim1();
//从第1列一直遍历到倒数第二列
//将对角线之下所以元素都填充0
for (Index j = 0; j<n - 1; ++j) {
const double pivot = A(j, j);
if (pivot == 0)cerr<<"错误:其中有一对角线位为0"<<'\n'; //将第i行在对角线之下的元素都填充为0
for (Index i = j + 1; i<n; ++i) {
const double mult = A(i, j) / pivot;
A[i].slice(j) = scale_and_add(A[j].slice(j), -mult, A[i].slice(j));
b(i) -= mult*b(j); //对b做对应变化
}
}
}
Vector back_substitution(const Matrix2&A, const Vector&b) {
const Index n = A.dim1();
Vector x(n); for (Index i = n - 1; i >= 0; --i) {
double s = b(i) - dot_product(A[i].slice(i + 1), x.slice(i + 1)); if (double m = A(i, i))
x(i) = s / m;
else
;
}
return x;
} void elim_with_partial_pivot(Matrix2& A, Vector& b) {
const Index n = A.dim1(); for (Index j = 0; j < n; ++j) {
Index pivot_row = j; //查找一个合适的主元:
for (Index k = j + 1; k < n; ++k)
if (abs(A(k, j)) > abs(A(pivot_row, j))) pivot_row = k; //如果我们找到了一个更好的主元,交换两行:
if (pivot_row != j) {
A.swap_rows(j, pivot_row);
std::swap(b(j), b(pivot_row));
} //消去:
for (Index i = j + 1; i < n; ++i) {
const double pivot = A(j, j);
if (pivot == 0)error("can't solve:pivot==0");
const double mult = A(i, j) / pivot;
A[i].slice(j) = scale_and_add(A[j].slice(j), -mult, A[i].slice(j));
b(i) -= mult*b(j); }
}
}
Vector classic_gaussian_elimination(Matrix2 A, Vector b) {
elim_with_partial_pivot(A, b);
//classical_elimination(A, b);
return back_substitution(A, b);
}
Vector operator*(const Matrix2&m, const Vector&u) {
const Index n = m.dim1();
Vector v(n);
for (Index i = 0; i < n; ++i) v(i) = dot_product(m[i], u);
return v;
}
int max0 = 10;
Vector random_vector(Index n) {
Vector v(n);
default_random_engine ran{(unsigned int)(time(0)+2)};
uniform_int_distribution<> ureal{ 0,max0 };
for (Index i = 0; i < n; ++i)
{
v(i) = ureal(ran);
} return v;
}
Matrix2 random_matrix(Index n) {
Matrix2 v(n,n);
default_random_engine ran{ (unsigned int)time(0) };
uniform_int_distribution<> ureal{ 0,max0 };
for (Index i = 0; i < n; ++i) { for (Index j = 0; j < n; ++j) v[i][j] = ureal(ran);
} return v;
} void solve_random_system(Index n) {
Matrix2 A = random_matrix(n);
Vector b = random_vector(n);
cout << "A=" << A << '\n';
cout << "b=" << b << '\n';
try {
Vector x = classic_gaussian_elimination(A, b);
cout << "classic elim solution is x =" << x << '\n';
Vector v = A*x;
cout << "A*x=" << v << '\n';
}
catch (const exception& e) {
cerr << e.what() << '\n';
} }
int main() {
/*double val2[3][3] = {2,1,-1,-3,-1,2,-2,1,2 };
double val1[3] = {8,-11,-3 };
Matrix2 A(val2);
Vector b(val1);
cout<<classic_gaussian_elimination(A, b);
*/
solve_random_system(4);
}
c++程序设计原理与实践(进阶篇)
实现求解线性方程(矩阵、高斯消去法)------c++程序设计原理与实践(进阶篇)的更多相关文章
- (c++11)随机数------c++程序设计原理与实践(进阶篇)
随机数既是一个实用工具,也是一个数学问题,它高度复杂,这与它在现实世界中的重要性是相匹配的.在此我们只讨论随机数哦最基本的内容,这些内容可用于简单的测试和仿真.在<random>中,标准库 ...
- 函数形参为基类数组,实参为继承类数组,下存在的问题------c++程序设计原理与实践(进阶篇)
示例: #include<iostream> using namespace std; class A { public: int a; int b; A(int aa=1, int bb ...
- 函数返回值string与返回值bool区别------c++程序设计原理与实践(进阶篇)
为什么find_from_addr()和find_subject()如此不同?比如,find_from_addr()返回bool值,而find_subject()返回string.原因在于我们想说明: ...
- 数值限制------c++程序设计原理与实践(进阶篇)
每种c++的实现都在<limits>.<climits>.<limits.h>和<float.h>中指明了内置类型的属性,因此程序员可以利用这些属性来检 ...
- 有符号数和无符号数------c++程序设计原理与实践(进阶篇)
有符号数与无符号数的程序设计原则: 当需要表示数值时,使用有符号数(如 int). 当需要表示位集合时,使用无符号数(如unsigned int). 有符号数和无符号数混合运算有可能会带来灾难性的后果 ...
- bitest(位集合)------c++程序设计原理与实践(进阶篇)
标准库模板类bitset是在<bitset>中定义的,它用于描述和处理二进制位集合.每个bitset的大小是固定的,在创建时指定: bitset<4> flags; bitse ...
- 编码原则实例------c++程序设计原理与实践(进阶篇)
编码原则: 一般原则 预处理原则 命名和布局原则 类原则 函数和表达式原则 硬实时原则 关键系统原则 (硬实时原则.关键系统原则仅用于硬实时和关键系统程序设计) (严格原则都用一个大写字母R及其编号标 ...
- gets()scanf()有害------c++程序设计原理与实践(进阶篇)
最简单的读取字符串的方式是使用gets(),例如: char a[12]; gets(a); 但gets()和scanf()是有害的,曾经有大约1/4的成功黑客攻击是由于gets()和它的近亲scan ...
- 宏(使用注意事项、主要用途)------c++程序设计原理与实践(进阶篇)
使用宏的时候一定要小心:在c中没有真正有效的方法来避免使用宏,但宏带有严重的副作用,因为宏不遵守通常的c(或c++)作用域和类型规则——它只是一种文本替换. 宏的使用注意事项: 所以宏名全部大写. ...
随机推荐
- Secure CRT修改文件夹的颜色
secureCRT有一个很大的问题是,如果设置Emulation Terminal 为Linux模式,则ls的时候,目录的蓝色跟背景的黑色非常接近,很难看清楚,修改办法 option->Glob ...
- C++11 变量和函数的链接性
在全局变量前添加const或者static,则该变量链接性为内部,即文件内有效.可以使用extern声明为外部. 如果要让函数的链接性为内部,则函数声明和定义都应使用static关键字. 例子: te ...
- oracle 11g 导出空表
正常情况下,oracle11g的 exp命令无法导出空表,弥补这个缺陷的方法是 在空表创建之前,更改系统设置: show parameter deferred_segment_creation 查看, ...
- 浅层神经网络 反向传播推导:MSE softmax
基础:逻辑回归 Logistic 回归模型的参数估计为什么不能采用最小二乘法? logistic回归模型的参数估计问题不能“方便地”定义“误差”或者“残差”. 对单个样本: 第i层的权重W[i]维度的 ...
- Tornado 高并发源码分析之三--- Application 对象
Application 对象主要工作: 服务器启动时: 1.在新建一个app的时候,根据设置好的 URL 和回调函数 Handler 封装成URLSpec 对象 服务器运行时: 2.在请求到来,将 ...
- Win10 pip安装pycocotools报错解决方法(cl: 命令行 error D8021 :无效的数值参数“/Wno-cpp”)
参考: https://blog.csdn.net/chixia1785/article/details/80040172 https://blog.csdn.net/gxiaoyaya/articl ...
- 多个if和一个ifelse的区别
一个程序的要求如下,输入一个学生的数学成绩,如果大于等于60,那么就输出good,如果小于60那么输出not good int a scanf_s("%d",&a) if( ...
- while循环 for循环的理解
不管是while循环还是for循环都隐含着一个if else的结构,就是说,if 条件满足,那么就执行循环体内部的语句,else就做循环体外部的事情. 有一个例子我觉得特别典型,程序内部定义了一个特定 ...
- Vue.js组件调用用及其组件通信
1.需要import,然后components注册.然后如下代码调用. <template> <header></header> //注册后才能这样使用 <b ...
- Cookie存中文乱码的问题
有个奇怪的问题:登录页面中使用Cookie存值,Cookie中要存中文汉字.代码在本地调试,一切OK,汉字也能顺利存到Cookie和从Cookie中读出,但是放到服务器上不管用了,好好的汉字成了乱码, ...