步骤:

其中A是一个n*n的系数方阵 向量xb分别是未知数和常量向量:

这个系统可能有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++程序设计原理与实践(进阶篇)的更多相关文章

  1. (c++11)随机数------c++程序设计原理与实践(进阶篇)

    随机数既是一个实用工具,也是一个数学问题,它高度复杂,这与它在现实世界中的重要性是相匹配的.在此我们只讨论随机数哦最基本的内容,这些内容可用于简单的测试和仿真.在<random>中,标准库 ...

  2. 函数形参为基类数组,实参为继承类数组,下存在的问题------c++程序设计原理与实践(进阶篇)

    示例: #include<iostream> using namespace std; class A { public: int a; int b; A(int aa=1, int bb ...

  3. 函数返回值string与返回值bool区别------c++程序设计原理与实践(进阶篇)

    为什么find_from_addr()和find_subject()如此不同?比如,find_from_addr()返回bool值,而find_subject()返回string.原因在于我们想说明: ...

  4. 数值限制------c++程序设计原理与实践(进阶篇)

    每种c++的实现都在<limits>.<climits>.<limits.h>和<float.h>中指明了内置类型的属性,因此程序员可以利用这些属性来检 ...

  5. 有符号数和无符号数------c++程序设计原理与实践(进阶篇)

    有符号数与无符号数的程序设计原则: 当需要表示数值时,使用有符号数(如 int). 当需要表示位集合时,使用无符号数(如unsigned int). 有符号数和无符号数混合运算有可能会带来灾难性的后果 ...

  6. bitest(位集合)------c++程序设计原理与实践(进阶篇)

    标准库模板类bitset是在<bitset>中定义的,它用于描述和处理二进制位集合.每个bitset的大小是固定的,在创建时指定: bitset<4> flags; bitse ...

  7. 编码原则实例------c++程序设计原理与实践(进阶篇)

    编码原则: 一般原则 预处理原则 命名和布局原则 类原则 函数和表达式原则 硬实时原则 关键系统原则 (硬实时原则.关键系统原则仅用于硬实时和关键系统程序设计) (严格原则都用一个大写字母R及其编号标 ...

  8. gets()scanf()有害------c++程序设计原理与实践(进阶篇)

    最简单的读取字符串的方式是使用gets(),例如: char a[12]; gets(a); 但gets()和scanf()是有害的,曾经有大约1/4的成功黑客攻击是由于gets()和它的近亲scan ...

  9. 宏(使用注意事项、主要用途)------c++程序设计原理与实践(进阶篇)

    使用宏的时候一定要小心:在c中没有真正有效的方法来避免使用宏,但宏带有严重的副作用,因为宏不遵守通常的c(或c++)作用域和类型规则——它只是一种文本替换.   宏的使用注意事项: 所以宏名全部大写. ...

随机推荐

  1. Excel开发学习笔记:文件选择控件、查找匹配项、单元格格式及数据有效性

    一个自用的基于excel的小工具. , ), .Cells(, ))          sysKpiRow.Interior.ColorIndex =  ).value = , )           ...

  2. U-boot分析与移植(2)----U-boot stage1分析

    我们要生成u-boot.bin文件,它首先依赖于很多.o文件和.lds链接脚本文件 我们只要找到对应的.lds链接脚本文件就可以分析u-boot的启动流程. 1.打开u-boot-1.1.6\u-bo ...

  3. Java学习之SpringBoot整合SSM Demo

    背景:在Java Web中Spring家族有着很重要的地位,之前JAVA开发需要做很多的配置,一堆的配置文件和部署调试一直是JavaWeb开发中的一大诟病,但现在Spring推出了SpringBoot ...

  4. 使用ReentrantReadWriteLock类

    读读共享 类ReentrantReadWriteLock的使用:写写互斥 读写互斥

  5. Iterator(迭代器)的一般用法 (转)

    迭代器(Iterator) 迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构.迭代器通常被称为“轻量级”对象,因为创建它的代价小. Java中的I ...

  6. 数据结构》关于差分约束的两三事(BZOJ2330)

    差分约束,主要用来解决数学中的线性规划问题,通过差值与两个未知数可以转化为单源最长路问题(或负值最短路). 当有一个式子为x1-x2>=a时,我们可以建边,这条边设定为x1比x2大等a(或x2比 ...

  7. Jquery中extend使用技巧

    在使用Jquery开发的过程中,extend是常用的参数处理函数,特别是对默认值的使用. Jquery的扩展方法原型是: var v=$.extend(dest,src1,src2,[,src3... ...

  8. 447. Number of Boomerangs 回力镖数组的数量

    [抄题]: Given n points in the plane that are all pairwise distinct, a "boomerang" is a tuple ...

  9. HttpSession解决表单的重复提交

    1). 重复提交的情况: ①. 在表单提交到一个 Servlet, 而 Servlet 又通过请求转发的方式响应一个 JSP(HTML) 页面, 此时地址栏还保留着 Serlvet 的那个路径, 在响 ...

  10. 7. Smali基础语法总结

    最近在学习Android 移动安全逆向方面,逆向首先要看懂代码,Android4.4之前一直使用的是 Dalivk虚拟机,而Smali是用于Dalivk的反汇编程序的实现. Smali 支持注解,调试 ...