C语言线性方程组求解:LU分解、雅可比迭代、高斯-赛德尔迭代

(注:该程序C和C++通用)

我们知道在Matlab中,矩阵的求解是非常方便的,对于Ax = b,只需要做x = A \ b的运算就可以得到x的结果,然而对于C语言,我们就需要寻找一些算法来实现线性矩阵的求解。

由于本学期刚学完数值分析,并且目前做的项目中刚好有需要求解线性方程组的需求,于是将求解线性方程组的三种方法(其实还有更多,我这里仅给出三种):LU分解、雅可比迭代以及高斯-赛德尔迭代。这三个算法的基本原理就不过多赘述了,大家想进一步学习的可以网上搜,一大堆教程,而且原理也较为简单。

下面给出该三个算法对应的函数:

(1)LU分解:
float* LU(float* A, float* b)
{
	//LU分解求方程的简单解释
	//系数矩阵各阶顺序主子式det(Ai)≠0时,可以进行LU分解
	//A = LU → Ax = b → LUx = b → Ly = b (其中Ux = y)
	//在计算得到LU矩阵后,先计算Ly = b
	//计算得到y后计算Ux = y就可以得到方程的解
	int i, j, n;
	float temp = 0;
	//生成一个二维数组保存LU分解所得的系数
	float** mat = (float**)malloc(N * sizeof(float*));
	for (i = 0; i < N; i++)
	{
		*(mat + i) = (float*)malloc(N * sizeof(float));
		// 将申请的数组中的值全部置零,方便后续使用
		memset(*(mat + i), 0, N * sizeof(float));
	}

	//为了节约内存,这里没有同时申请x和y,而是仅申请x,但这样也够用了
	float* x = (float*)malloc(N * sizeof(float));
	memset(x, 0, N * sizeof(float));

	//下面首先对LU矩阵的第一行和第一列进行计算,因为这两行有规律,容易写出
	i = 0;
	//LU分解矩阵的第一行和系数矩阵是一致的
	for (j = 0; j < N; j++)
		mat[i][j] = A[i * N + j];
	//LU的第一列结果可以通过除以第一列第一个(即A[0][0])计算得到
	j = 0;
	for (i = 1; i < N; i++)
		mat[i][j] = A[i * N + j] / A[0];
	//下面开始对LU其他位置的值进行求解
	for (i = 1; i < N; i++)
		for (j = 1; j < N; j++)
		{
			if (i <= j)
			{
				temp = 0;
				for (n = 0; n < i; n++)
					temp += mat[n][j] * mat[i][n];
				mat[i][j] = A[i * N + j] - temp;
			}
			else
			{
				temp = 0;
				for (n = 0; n < j; n++)
					temp += mat[n][j] * mat[i][n];
				mat[i][j] = (A[i * N + j] - temp) / mat[j][j];  //列上方若有对角值(即i = j),需要处于该对角值
			}
		}
	//首先计算Ly = b,这里是自上而下的计算
	//这里的x的就是y
	for (i = 0; i < N; i++)
	{
		temp = 0;
		for (j = 0; j < i; j++)
		{
			temp += mat[i][j] * x[j];
		}
		x[i] = b[i] - temp;
	}
	//然后计算Ux = y,这里是自下而上的计算
	for (i = N-1; i >= 0; i--)
	{
		temp = 0;
		for (j = N - 1; j > i; j--)
		{
			temp += mat[i][j] * x[j];
		}
		x[i] = (x[i] - temp)/mat[i][i];
	}

	//free(x);(×)     //这里切记不要释放x,否则计算结果会消失,可以在之后的主函数中释放
	free(mat);  //释放内存
	return x;   //将计算结果返回
}

LU分解可以获得线性方程的精确解

(2)雅可比迭代算法
//雅可比迭代函数
float* Jacobi(float* A, float* b)
{
	int i, j;
	int iter_num = 0;   //目前的迭代次数
	float error_now = 100;   //目前迭代下的误差

	//生成数组用于保存每次迭代得到的x结果并保存上一次迭代结果至x0
	float* x = (float*)malloc(N * sizeof(float));
	memset(x, 0, N * sizeof(float));
	float* x0 = (float*)malloc(N * sizeof(float));
	memset(x0, 0, N * sizeof(float));

	while(error_now > ERROR)
	{
		//将上一次迭代的结果保存到x0中去
		memcpy(x0, x, N * sizeof(float));

		for (i = 0; i < N; i++)
		{
			float sum = 0;
			for (j = 0; j < N; j++)
			{
				//printf("%f\t", A[i * N + j]);
				if (i != j)
					sum += A[i * N + j] * (x0[j]);
			}
			x[i] = (b[i] - sum) / A[i * N + i];
		}

		error_now = current_error(x, x0);
		iter_num++;

		printf("当前迭代次数为:%d,迭代误差为:%f\n", iter_num, error_now);
		if (iter_num > IterMaxNum)
		{
			printf("迭代次数超过最大值要求,返回最后一次迭代结果");
			break;
		}
	}

	free(x0);
	return x;
}

雅可比迭代可以获得线性方程组的近似解,近似程度和我们所设置ERROR误差参数有关,越小接近程度越好,但迭代的次数就会越多。

其中的current_error函数如下,用于计算前后两次迭代之间的误差,当误差小于我们所设置的ERROR参数时,迭代就会结束,返回迭代结果。

float current_error(float* last, float* now)
{
	float max_error = 0;
	float temp;
	for (int i = 0; i < N; i++)
	{
		temp = *(now + i) - *(last + i);
		//这里不用abs的原因:abs不知道为什么一直报错,说是函数重载
		if (temp < 0)
			temp = -temp;
		if (temp > max_error)
			max_error = temp;
	}
	return max_error;
}

这里我用的是Visual Studio 2022版本,我引入头文件<stdlib.h>后abs函数一直报错,不知道为什么,于是我就换了一种写法,多了一步if负数判断。

(3)高斯-赛德尔迭代法
//高斯-赛德尔迭代函数
float* GS(float* A, float* b)
{
	int i, j;
	int iter_num = 0;   //目前的迭代次数
	float error_now = 100;   //目前迭代下的误差

	//生成数组用于保存每次迭代得到的x结果并保存上一次迭代结果至x0
	float* x = (float*)malloc(N * sizeof(float));
	memset(x, 0, N * sizeof(float));
	float* x0 = (float*)malloc(N * sizeof(float));
	memset(x0, 0, N * sizeof(float));

	while (error_now > ERROR)
	{
		memcpy(x0, x, N * sizeof(float));

		for (i = 0; i < N; i++)
		{
			float sum = 0;
			for (j = 0; j < N; j++)
			{
				//printf("%f\t", A[i * N + j]);
				if (i != j)
					sum += A[i * N + j] * (x[j]);
			}
			x[i] = (b[i] - sum) / A[i * N + i];
		}

		error_now = current_error(x, x0);  //获得当前迭代的误差结果
		iter_num++;   //迭代次数加1

		printf("当前迭代次数为:%d,迭代误差为:%f\n", iter_num, error_now);    //输出当前迭代信息
		if (iter_num > IterMaxNum)
		{
			printf("迭代次数超过最大值要求,返回最后一次迭代结果");
			break;
		}
	}

	free(x0);
	return x;
}

高斯-赛德尔迭代法的程序和上面的雅可比迭代法的程序是非常相似的,但高斯-赛德尔是雅可比的改进算法,比雅可比有更快的收敛速度(即迭代次数更少)。

(4)一个使用例程

下面我给出了一个完整的使用例程,利用#ifdef函数来判断你想运行的算法,只需要在前面把你想运行的方法取消注释,不用的方法打上注释即可。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

#define N 4   //要求解的数组大小N x N
#define ERROR 0.00001  //误差要求
#define IterMaxNum 50000   //最大迭代次数

#define iter_Jacobi   //运行雅可比迭代(非常近似的解)
//#define iter_GS    //运行高斯-赛德尔迭代(非常近似的解)
//#define LU_Count   //运行LU分解求解方程解(精确解)

float current_error(float* last, float* now)
{
	float max_error = 0;
	float temp;
	for (int i = 0; i < N; i++)
	{
		temp = *(now + i) - *(last + i);
		//这里不用abs的原因:abs不知道为什么一直报错,说是函数重载
		if (temp < 0)
			temp = -temp;
		if (temp > max_error)
			max_error = temp;
	}
	return max_error;
}

//雅可比迭代函数
float* Jacobi(float* A, float* b)
{
	int i, j;
	int iter_num = 0;   //目前的迭代次数
	float error_now = 100;   //目前迭代下的误差

	//生成数组用于保存每次迭代得到的x结果并保存上一次迭代结果至x0
	float* x = (float*)malloc(N * sizeof(float));
	memset(x, 0, N * sizeof(float));
	float* x0 = (float*)malloc(N * sizeof(float));
	memset(x0, 0, N * sizeof(float));

	while(error_now > ERROR)
	{
		//将上一次迭代的结果保存到x0中去
		memcpy(x0, x, N * sizeof(float));

		for (i = 0; i < N; i++)
		{
			float sum = 0;
			for (j = 0; j < N; j++)
			{
				//printf("%f\t", A[i * N + j]);
				if (i != j)
					sum += A[i * N + j] * (x0[j]);
			}
			x[i] = (b[i] - sum) / A[i * N + i];
		}

		error_now = current_error(x, x0);
		iter_num++;

		printf("当前迭代次数为:%d,迭代误差为:%f\n", iter_num, error_now);
		if (iter_num > IterMaxNum)
		{
			printf("迭代次数超过最大值要求,返回最后一次迭代结果");
			break;
		}
	}

	free(x0);
	return x;
}

//高斯-赛德尔迭代函数
float* GS(float* A, float* b)
{
	int i, j;
	int iter_num = 0;   //目前的迭代次数
	float error_now = 100;   //目前迭代下的误差

	//生成数组用于保存每次迭代得到的x结果并保存上一次迭代结果至x0
	float* x = (float*)malloc(N * sizeof(float));
	memset(x, 0, N * sizeof(float));
	float* x0 = (float*)malloc(N * sizeof(float));
	memset(x0, 0, N * sizeof(float));

	while (error_now > ERROR)
	{
		memcpy(x0, x, N * sizeof(float));

		for (i = 0; i < N; i++)
		{
			float sum = 0;
			for (j = 0; j < N; j++)
			{
				//printf("%f\t", A[i * N + j]);
				if (i != j)
					sum += A[i * N + j] * (x[j]);
			}
			x[i] = (b[i] - sum) / A[i * N + i];
		}

		error_now = current_error(x, x0);  //获得当前迭代的误差结果
		iter_num++;   //迭代次数加1

		printf("当前迭代次数为:%d,迭代误差为:%f\n", iter_num, error_now);    //输出当前迭代信息
		if (iter_num > IterMaxNum)
		{
			printf("迭代次数超过最大值要求,返回最后一次迭代结果");
			break;
		}
	}

	free(x0);
	return x;
}

//利用LU分解求解方程解
float* LU(float* A, float* b)
{
	//LU分解求方程的简单解释
	//系数矩阵各阶顺序主子式det(Ai)≠0时,可以进行LU分解
	//A = LU → Ax = b → LUx = b → Ly = b (其中Ux = y)
	//在计算得到LU矩阵后,先计算Ly = b
	//计算得到y后计算Ux = y就可以得到方程的解
	int i, j, n;
	float temp = 0;
	//生成一个二维数组保存LU分解所得的系数
	float** mat = (float**)malloc(N * sizeof(float*));
	for (i = 0; i < N; i++)
	{
		*(mat + i) = (float*)malloc(N * sizeof(float));
		// 将申请的数组中的值全部置零,方便后续使用
		memset(*(mat + i), 0, N * sizeof(float));
	}

	//为了节约内存,这里没有同时申请x和y,而是仅申请x,但这样也够用了
	float* x = (float*)malloc(N * sizeof(float));
	memset(x, 0, N * sizeof(float));

	//下面首先对LU矩阵的第一行和第一列进行计算,因为这两行有规律,容易写出
	i = 0;
	//LU分解矩阵的第一行和系数矩阵是一致的
	for (j = 0; j < N; j++)
		mat[i][j] = A[i * N + j];
	//LU的第一列结果可以通过除以第一列第一个(即A[0][0])计算得到
	j = 0;
	for (i = 1; i < N; i++)
		mat[i][j] = A[i * N + j] / A[0];
	//下面开始对LU其他位置的值进行求解
	for (i = 1; i < N; i++)
		for (j = 1; j < N; j++)
		{
			if (i <= j)
			{
				temp = 0;
				for (n = 0; n < i; n++)
					temp += mat[n][j] * mat[i][n];
				mat[i][j] = A[i * N + j] - temp;
			}
			else
			{
				temp = 0;
				for (n = 0; n < j; n++)
					temp += mat[n][j] * mat[i][n];
				mat[i][j] = (A[i * N + j] - temp) / mat[j][j];  //列上方若有对角值(即i = j),需要处于该对角值
			}
		}
	//首先计算Ly = b,这里是自上而下的计算
	//这里的x的就是y
	for (i = 0; i < N; i++)
	{
		temp = 0;
		for (j = 0; j < i; j++)
		{
			temp += mat[i][j] * x[j];
		}
		x[i] = b[i] - temp;
	}
	//然后计算Ux = y,这里是自下而上的计算
	for (i = N-1; i >= 0; i--)
	{
		temp = 0;
		for (j = N - 1; j > i; j--)
		{
			temp += mat[i][j] * x[j];
		}
		x[i] = (x[i] - temp)/mat[i][i];
	}

	//free(x);(×)     //这里切记不要释放x,否则计算结果会消失,可以在之后的主函数中释放
	free(mat);  //释放内存
	return x;   //将计算结果返回
}

void main()
{
	//这里给的例子为:
	//8a - 3b + 2c = 20
	//4a + 11b - c = 33
	//6a + 3b + 12c = 36
	//float A[N][N] = { {8,-3,2},{4,11,-1},{6,3,12} };  //要求解的矩阵
	//float b[N] = { 20,33,36 };
	//该方程精确解为 a = 3 、b = 2 、c = 1
	
	//另外一个例子
	float A[N][N] = { {1,0,2,0},{0,1,0,1},{1,2,4,3},{0,1,0,3} };   //要求解的矩阵
	float b[N] = { 5,3,17,7 };
	//该方程精确解为 a = 1 、b = 1 、c = 2 、d = 2

	float* x;
#ifdef LU_Count
	x = LU((float*)A, b);
#endif

#ifdef iter_Jacobi
	x = Jacobi((float*)A, b);
#endif
	
#ifdef iter_GS
	x = GS((float*)A, b);
#endif
	for (int i = 0; i < N; i++)
		printf("%f\n", x[i]);
	free(x);   //释放x
}
(5)小结

总之,想要精确解就LU算法,想要程序运行更快就选择迭代法(当然,前提是需要满足其分解条件和迭代条件)。

  • 11
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值