实现求解线性方程(矩阵、高斯消去法)------c++程序设计原理与实践(进阶篇)

步骤:

其中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++程序设计原理与实践(进阶篇)

时间: 2024-11-07 17:23:04

实现求解线性方程(矩阵、高斯消去法)------c++程序设计原理与实践(进阶篇)的相关文章

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

随机数既是一个实用工具,也是一个数学问题,它高度复杂,这与它在现实世界中的重要性是相匹配的.在此我们只讨论随机数哦最基本的内容,这些内容可用于简单的测试和仿真.在<random>中,标准库提供了复杂的方法来产生适应不同数学分布的随机数.这一随机数标准库基于下面两个基础概念: 发生器(engine,随机数发生器):发生器是一个可以产生均匀分布整形值序列的函数对象. 分布(distribution):分布是一个函数对象,给定一个发生器产生的序列作为输入,分布可以按照相应数学公式产生一个值的序列.

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

编码原则: 一般原则 预处理原则 命名和布局原则 类原则 函数和表达式原则 硬实时原则 关键系统原则 (硬实时原则.关键系统原则仅用于硬实时和关键系统程序设计) (严格原则都用一个大写字母R及其编号标识,而推荐原则都用小写字母r及其编号标识,对于前者程序员必须严格遵守,而后者则偶尔可以不遵守) 1.一般原则 R100:任何函数和类的代码规模都不应超过200行(不包括注释). 原因:长的函数和类会更复杂,因而难以理解和测试. r101:任何函数和类都应该能完全显示在一屏上,并完成单一的逻辑功能.

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

标准库模板类bitset是在<bitset>中定义的,它用于描述和处理二进制位集合.每个bitset的大小是固定的,在创建时指定: bitset<4> flags; bitset<128> dword_bits; bitset<12345> lots; 默认情况下,bitset被初始化为全0,但通常我们都会给它一个初始值,可以是一个无符号的整数或者"0"和"1"组成的字符串.例如: bitset<4> fl

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

有符号数与无符号数的程序设计原则: 当需要表示数值时,使用有符号数(如 int). 当需要表示位集合时,使用无符号数(如unsigned int). 有符号数和无符号数混合运算有可能会带来灾难性的后果.例如: vector<int> v; for(int i=0;i<v.size();++i)cout<<v[i]<<'\n'; 易实现版本: unsigned char max=160; //非常大 for(signed char i=0;i<max;i++)

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

每种c++的实现都在<limits>.<climits>.<limits.h>和<float.h>中指明了内置类型的属性,因此程序员可以利用这些属性来检查数值限制.设置哨兵机制等等.它们对于开发底层程序是非常重要的.如果你觉得需要这些属性值,表明你的工作很可能比较靠近硬件.但这些属性还有其他用途,例如,对语言实现细节感到好奇是很正常的:"一个int有多大?","char是有符号的吗?"等等.希望从系统文档中找到这些问题

《C++程序设计原理与实践》读书笔记(一)

程序设计是这样一门艺术,它将问题求解方案描述成计算机可以执行的形式.程序设计中很多工作都花费在求解方案以及对其求精上.通常,只须在真正编写程序求解一个问题的过程中才会对问题本身理解透彻.   为什么学习C++这门程序设计语言呢?学习程序设计不可能不借助一门程序设计语言,而C++直接支持现实世界中的软件所使用的那些关键概念和技术.C++是使用最为广泛的程序设计语言之一,其应用领域几乎没有局限.从大洋深处到火星表面,到处都能发现C++程序的身影.C++是由一个开放的国际标准组织全面考量.精心设计的.

《C++程序设计原理与实践》读书笔记(三)

一个显示模型 一种图形及图形用户界面直接对应屏幕显示的思想.其基本概念先天就是图形化的(而且都是二维的,适应计算机屏幕的矩形区域),这些基本概念包括坐标.线.矩形和圆等.从编程的角度看,其目的是建立内存中的对象和屏幕图像的直接对应关系. 其基本模型如下:我们利用图形系统提供的基本对象(如线)组合出更复杂的对象:然后将这些对象添加到一个表示物理屏幕的窗口对象中:最后,用一个程序将我们添加到窗口上的对象显示在屏幕上,我们可以将这个程序看做屏幕显示本身,或者是一个"显示引擎",或者是&quo

C++程序设计原理与实践 第十九章部分答案

1 #include <iostream> 2 #include <vector> 3 #include <string> 4 using namespace std; 5 6 template<class T> void f(vector<T>&v1,vector<T>&v2) //v2不设为const因为可能v2=v1 习题1 7 { 8 if(v1.capacity()<=v2.size()) 9 { 10

C++程序设计原理与实践 第十章部分答案

1 #include "../../st.h" 2 3 class Point{ 4 public: 5 Point():x(0),y(0){} 6 Point(int x1,int y1):x(x1),y(y1){} 7 double re_x(){return x;} 8 double re_y(){return y;} 9 friend istream& operator>>(istream&is,Point&p); 10 private: 1