数值分析(三):C++实现线性方程组的高斯-赛德尔迭代法

线性方程组的直接解法之后,就轮到迭代解法了,直接解法针对的是低阶稠密矩阵,数据量较少,而工程上有更多的是高阶系数矩阵,使用迭代法效率更高,占用的空间较小。
迭代法的最基本思想就是由初始条件,比如说初始解向量随便列举一个,就0向量也行,然后进行迭代,k到k+1,一步一步从k=1开始去逼近真实解,虽然说迭代法的解是近似解,但是当迭代次数足够多的时候,得到的就是很很接近真实解了,而直接解法说是得到精确解,但是受计算机的限制,截断误差总是有的,所以最后得到的也是近似解,因此两种解法也无所谓孰优孰劣之分,得具体情境具体看了,这次来实现迭代法中的高斯-赛德尔迭代法,并用C++实现。
(一)高斯-赛德尔迭代法的步骤
最简单的迭代法是雅克比迭代法,对于对角严格占优的系数矩阵就可以使用雅克比迭代法进行计算,比如说方程:
图1 实例方程
满足系数矩阵的严格对角占优,即对角线上的元素比同行的其他元素之和还要大,数学表达式就是:
图2 系数矩阵严格对角占优的数学表述
满足这样的关系就可以进行雅克比迭代法了,进行雅克比迭代是留下主对角元素在最左边,然后剩下全部移到右边,得到这样一个一步一步迭代的式子:
图3 雅克比迭代递推
这样就到了一个k到k+1的迭代公式,可以取(0,0,0)进行迭代,知道解向量达到一定的精度放弃迭代,十次迭代后,就能得到:
图4 雅克比迭代法得到近似解
这是迭代法最常见也最容易的雅克比迭代,效率比较低,于是就有高斯—赛德尔迭代法,那就是方程里迭代计算x2时,x1已经迭代过了,即x1的数据已经更新了,所以应该用最新的值,关键是效率会高很多,于是就有下面的迭代式子:
图5 雅克比迭代法的改进就是高斯-赛德尔迭代法
这样一种小小的改进,给迭代计算方程的解带来极大效率的提高,所以接下来再将高斯—赛德尔迭代法步骤进行数学语言的描述:
使用高斯-赛德尔迭代法解线性方组程***Ax=b***:
1:先判断系数矩阵是否严格对角占优,即针对系数矩阵的每一行验证,是否满足主对角元的绝对值比该行其他元素的绝对值之和还要大,即在循环里验证系数矩阵每一行是否满足:

满足了对角占优,再继续下一步;
2:
(1)设定初始即第一次迭代时,k=1时的解向量,方便起见,x[i]=0(i=1,2,···n),初始向量设定为零向量;
(2)对于k=1,2,3···N(N视你的精度确定,若是精确到小数点之后很多位,那N就比较大了),然后对于i=1,2,···n:
图6:高斯-赛德尔迭代法最关键的算式
当满足了精度要求就退出循环,计算结束。
(二)C++实现高斯-赛德尔迭代法
同样的分别用函数和类来实现这个简单的算法,首先是函数,还是那个观点,函数就是一个黑匣子,对于客户或是任何一个使用者来说,只需要知道输入参数是什么,以及自己需要得到什么结果就行,至于黑匣子中间是什么,以及如何实现的,就不用你操心了,还记得以前做毕设的时候,有一步是关于工业相机二次开发的,当然二次开发这个名词有点装13,但是事实上就是调用厂家提供的函数自己写段代码,或是类来控制相机,控制相机的采集图像,曝光时间、白平衡等属性的控制,以及数据的保存,当时就很不理解,一个是二次开发有什么用?明明厂家都提供驱动了,直接打开驱动直接采集不就行了?答案就是人家驱动的源代码不可能给你啊;二就是厂家就提供一个开发包给你,开发包里面是什么也不知道,就像一个黑匣子一样,你用就知道了,没必要知道里面是什么,当时就很心慌,内部机理都不知道就让我操作,当时还是太年轻,哎~现在都看开了,因为底层的东西,真的工作量会很大,而且你也不一定能看懂,更关键的是你也没必要重复造轮子,当然也造不出来,还不如人家写的是什么,你就用什么,既然商业化的产品,正规的大厂,品质自然能保证,所以用就行了。回到函数,从设计者的角度来看,这个高斯-赛德尔迭代法需要哪些参数,系数矩阵、常数矩阵、矩阵阶数,线性方程的表示就这些,还有相比直接解法,迭代解法跟重要的就是解的精确度要求,所以输入就是这么四个参数,输出吗,就是一个解向量,于是函数的声明如下:

double *gauss_seidel(int n, double **a, double *b,double eps)

最后将数学语言翻译成C++语言就可以得到高斯-赛德尔迭代法的编程实现了,比想象中的来得简单:

double *gauss_seidel(int n, double **a, double *b,double eps)
{
	int i, j;
	double p, t, s, q;
	double *x = new double[n];

	for (i = 0; i <= n - 1; i++)//这个大循环判断系数矩阵是否严格对角占优,是否适用高斯赛德尔迭代法
	{
		p = 0.0; x[i] = 0.0;
		for (j = 0; j <= n - 1; j++)
			if (i != j)   p = p + fabs(a[i][j]);
		if (p >= fabs(a[i][i]))
		{
			cout << "\n程序工作失败!" << endl;
			return NULL;
		}
	}
	//接下来就是正式的迭代计算
	p = eps + 1.0;
	while (p >= eps)//当精度还没达到的时候,继续迭代
	{
		p = 0.0;
		for (i = 0; i <= n - 1; i++)
		{
			t = x[i]; s = 0.0;
			for (j = 0; j <= n - 1; j++)
				if (j != i) s = s + a[i][j] * x[j];
			x[i] = (b[i] - s) / a[i][i];
			q = fabs(x[i] - t) / (1.0 + fabs(x[i]));
			if (q>p) p = q;
		}
	}
	return x;
}

其中,编程实现反而较雅克比迭代法简单,因为循环中的值本来就是持续更新的,所以在计算k+1次的时候,用到的就是最新的数据,所以,反倒很容易实现,计算机倒是非常对高斯-赛德尔迭代法的胃口。
笔者的主函数调用如下:

int main()
{
	double **A, *b, eps;
	int n;
	cout << "输入系数矩阵的阶数n:" << endl;
	cin >> n;
	cout << "依次输入系数矩阵的每一个元素A[i][j]:" << endl;
	A = new double *[n];
	for (int i = 0; i < n; i++)
		A[i] = new double[n];
	for (int i = 0; i < n; i++)
		for (int j = 0; j < n; j++)
			cin >> A[i][j];
	b = new double[n];
	cout << "输入常数向量每一项b[i]:" << endl;
	for (int i = 0; i < n; i++)
		cin >> b[i];
	double *x = new double[n];
	cout << "输入计算解的精度要求eps:" << endl;
	cin >> eps;
	x = gauss_seidel(n, A, b, eps);
	cout << "通过高斯—赛德尔迭代法计算得到的解:" << endl;
	for (int i = 0; i < n; i++)
		cout << "x"<<i<<"="<<x[i] <<endl;
	system("pause");
	return 0;
}

当然写成类就没必要在主函数这么矫情了,直接用txt文件进行数据的读入,输入的结果也保存在txt文件里面,类的实现如下:

//3GSDL.CPP
//Gauss-Seidel迭代法
#include  <iostream>
#include  <fstream>
#include  <cmath>
using namespace std;

class  gsdl
{
private:
	int n;
	double  **a, *b, *x, eps;
public:
	gsdl(int nn)
	{
		int i;
		n = nn;
		a = new double*[n];   //动态分配内存空间
		for (i = 0; i<n; i++) a[i] = new double[n];
		b = new double[n];
		x = new double[n];
	}
	void input();    //从文件读入系数矩阵A、常数向量B以及eps
	void a_gsdl();    //执行Gauss-Seidel迭代法
	void output();   //输出结果到文件并显示
	~gsdl()
	{
		int i;
		for (i = 0; i<n; i++) { delete[] a[i]; }
		delete[] a;
		delete[] b, x;
	}
};

void gsdl::input()      //从文件读入系数矩阵A、常数向量B以及eps
{
	int  i, j;
	char str1[20];
	cout << "\n输入文件名:  ";
	cin >> str1;
	ifstream  fin(str1);
	if (!fin)
	{
		cout << "\n不能打开这个文件 " << str1 << endl; exit(1);
	}
	for (i = 0; i<n; i++)                       //读入矩阵A
		for (j = 0; j<n; j++)  fin >> a[i][j];
	for (i = 0; i<n; i++)  fin >> b[i];          //读入常数向量B
	fin >> eps;                              //读入eps
	fin.close();
}

void gsdl::a_gsdl()       //执行Gauss-Seidel迭代法
{
	int i, j;
	double p, t, s, q;
	for (i = 0; i <= n - 1; i++)
	{
		p = 0.0; x[i] = 0.0;
		for (j = 0; j <= n - 1; j++)
			if (i != j)   p = p + fabs(a[i][j]);
		if (p >= fabs(a[i][i]))
		{
			cout << "\n程序工作失败!" << endl;
			return;
		}
	}
	p = eps + 1.0;
	while (p >= eps)
	{
		p = 0.0;
		for (i = 0; i <= n - 1; i++)
		{
			t = x[i]; s = 0.0;
			for (j = 0; j <= n - 1; j++)
				if (j != i) s = s + a[i][j] * x[j];
			x[i] = (b[i] - s) / a[i][i];
			q = fabs(x[i] - t) / (1.0 + fabs(x[i]));
			if (q>p) p = q;
		}
	}
}

void gsdl::output()       //输出结果到文件并显示
{
	int  i;
	char str2[20];
	cout << "\n输出文件名:  ";
	cin >> str2;
	ofstream fout(str2);
	if (!fout)
	{
		cout << "\n不能打开这个文件 " << str2 << endl; exit(1);
	}
	fout << endl;  cout << endl;
	for (i = 0; i<n; i++)
	{
		fout << x[i] << "   ";
		cout << x[i] << "   ";
	}
	fout << endl;  cout << endl;
	fout.close();
}

void main()      //主函数
{
	gsdl  c(3);
	c.input();         //从文件读入系数矩阵A、常数向量B以及eps
	c.a_gsdl();        //执行Gauss-Seidel迭代法
	c.output();        //输出结果到文件并显示
	system("pause");
}

就这样完成了高斯-赛德尔迭代法的C++实现,今天貌似心情不好啊,(*  ̄︿ ̄),写点东西没点激情,有点丧,其实丧真的是常态,前阵子听个简单的讲座,关于如何克服拖延的,结果老师到场,就直接说道,这主题不对啊,你们现在还有心情探讨怎么克服拖延,在我看来,你们就是矫情,你们就是太闲了,没有体会过那种事情办砸了的糟糕体验,以及没有感受到竞争的激烈性,一片厮杀的红海了,哪还有心情拖延,最好立即、马上去学习,不知道学什么的话,最起码把英语学好,哈哈哈哈,蛮有道理(事情应该被夸张了,不过意思差不多啦,这样叙述反而更有表现力,反正是正能量的东西,就不想较真了。)虽然我不认为自己是个拖延的人,但是还是觉得好有道理,笔者还是好好培养自己的执行力了~

猜你喜欢

转载自blog.csdn.net/baidu_41647951/article/details/83578658