实现求解线性方程(矩阵、高斯消去法)------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++)作用域和类型规则——它只是一种文本替换. 宏的使用注意事项: 所以宏名全部大写. ...
随机推荐
- zabbix的sendEmail配置
zabbix的sendEmail配置 [root@hongquan scripts]# yum install sendmail[root@hongquan soft]# tar xvzf sendE ...
- Metasploit自动化脚本Ezsploit
打开文件夹并赋予权限 ┌─[root@sch01ar]─[~] └──╼ #cd /sch01ar/ezsploit/ && ls ezsploit.sh README.md ┌─[r ...
- Python Twisted系列教程7:小插曲,Deferred
作者:dave@http://krondo.com/an-interlude-deferred/ 译者:杨晓伟(采用意译) 你可以从这里从头开始阅读这个系列 回调函数的后序发展 在第六部分我们认识这 ...
- py编码终极版
说起python编码,真是句句心酸.算起来,反复折腾两个来月了.万幸的是,终于梳理清楚了.作为一个共产主义者,一定要分享给大家.如果你还在因为编码而头痛,那么赶紧跟着我咱们一起来揭开py编码的真相吧! ...
- Python——通过斐波那契数列来理解生成器
一.生成器(generator) 先来看看一个简单的菲波那切数列,出第一个和第二个外,任意一个数都是由前两个数相加得到的.如:0,1,1,2,3,5,8,13...... 输入斐波那契数列前N个数: ...
- Java面向对象-类与对象
Java面向对象-类与对象 类与对象的关系 我们通俗的举个例子,比如人类是一种类,张三这个人就是人类的具体的一个个体,也就是java中的对象:这就是一个类与对象的关系: 类的定义 下面看实例 类的创建 ...
- 【278】◀▶ Python 数学函数说明
参考:Python 数学函数说明 目录: 一.Python 数学函数 二.Python 随机数函数 三.Python 三角函数 四.Python 数学常量 一.Python 数学函数 函数 返回值 ...
- Redis搭建(四):Sharding集群模式
一. 方案 1. 介绍redis集群分为服务端集群(Cluster)和客户端分片(Sharding)服务端集群:redis3.0以上版本实现,使用哈希槽,计算key的CRC16结果再模16834.此处 ...
- Project2--Lucene的Ranking算法修改:BM25算法
原文出自:http://blog.csdn.net/wbia2010lkl/article/details/6046661 1. BM25算法 BM25是二元独立模型的扩展,其得分函数有很 ...
- js 线程和进程的关系
进程(process)和线程(thread)是操作系统的基本概念 1.计算机的核心是CPU,它承担了所有的计算任务 2.单个CPU一次只能运行一个任务 3.进程它代表CPU所能处理的单个任务.任一时刻 ...