1. 描述
高斯消元法是将方程组中的一方程的未知数用含有另一未知数的代数式表示,并将其代入到另一方程中,这就消去了一未知数,得到一解;或将方程组中的一方程倍乘某个常数加到另外一方程中去,也可达到消去一未知数的目的。消元法主要用于二元一次方程组的求解。
核心
1) 两方程互换,解不变;
2) 一方程乘以非零数k,解不变;
3) 一方程乘以数k加上另一方程,解不变
2. 人工求解方式
人工求解方式如下:
3.程序求解
只要理解了算法思路,用不同语言实现代码过程类似。算法思想如下:
1.设置变量colNum值为矩阵列数,则可利用一维数组存储矩阵中所有元素。
2.选择主元,即选择对角元素,保证其绝对值为所在列的最大,避免大数除以小数出现溢出,其实是避免其值为0。
3.当前主元不为最大值,交换两行。
4.判断主元是否为0,若是则不是唯一解。
5.逐行消元。
6.对角线元素归一化。
7.回代消除对角线之上的元素。
其中会产生一些疑问:
3.1 为何要选择主元?
在计算机执行消去法时,要将一行是第一个元素化为1,即该行各元素同除以第一个非零元素,如果这个元素的绝对值非常小,就会导致该行其它元素变得绝对值非常大,在与其它行对应元素执行加减运算时会把其它行的元素忽略不计,使得误差增大,为了避免出现这种情况,编制程序的时候增加了选主元的步骤。这主要是因为计算机进行的是近似计算,如果是用准确数运算,选主元的步骤是不必要的。
3.2 为什么计算机进行的是近似计算?
因为计算机存储小数的方式决定了计算机在进行浮点数运算时有可能存在误差。例如一个经典面试题,在计算机中,使用IEEE754标准存储浮点数时计算结果如下:
0.1 + 0.2 = 0.30000000000000004
因为 0.1和0.2在使用IEEE754标准存储时,在计算机中存储结果为:
0.1: 0.00011001100110011……(循环0011)
0.2: 0.0011001100110011…….(循环0011)
但计算机不可能存储无限长度的数,故会根据设置的数据格式长度来截取,32或64位,这样就会造成数据精度丢失。
3.3 代码实现
3.3.1 C语言
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
bool resolveByGauss(double[], int, int);
void printArrayInfo(double[], int, int);
int selectPivotalElement(double[], int, int, int);
void swapCol(double[], int, int, int);
void emissElement(double[], int, int, int);
void elementBack(double[], int, int, int);
void backEmiss(double[], int, int);
void printArrayInfo(double[], int);
int main(int argc, char *argv[]) {
//创建题中所示矩阵
double array[12] = {1, -3, 5, 0, 2, -1, -3, 11, 2, 1, -3, 5};
//计算数组长度
int arrayLength = sizeof(array) / sizeof(array[0]);
printf("题中的增广矩阵\n");
printArrayInfo(array, 4, arrayLength);
//执行运算
resolveByGauss(array, 4, arrayLength);
printf("高斯消元后的矩阵\n");
printArrayInfo(array, 4, arrayLength);
}
/**
* 利用高斯消元法计算
* @param array 系数矩阵 增广矩阵
* @param colNum 列向量个数(从第0列开始)
* @param arrayLength 数组的长度
* @return boolean 是否得出结果
*/
bool resolveByGauss(double array[], int colNum, int arrayLength) {
//行数
int rows = arrayLength / colNum;
//逐行选择主元 第 i 行 ,第i列 的元素(i,i)
for (int i = 0; i < rows; i++) {
//按列选择主元
int pivotRow = selectPivotalElement(array, colNum, i, arrayLength);
//如果主元不是目前所在的i列,则交换
if (pivotRow != i) {
swapCol(array, pivotRow, i, colNum);
}
if (array[i * colNum + i] == 0) {
printf("不含有唯一解");
return false;
}
//消元得到上三角
for (int j = 0; j < colNum; j++) {
emissElement(array, j, colNum, arrayLength);
}
}
//主元素归一化
for (int i = 0; i < rows; i++) {
elementBack(array, i, colNum, arrayLength);
}
//回代消元,从最后一行开始
for (int i = rows - 1; i > 0; i--) {
backEmiss(array, i, colNum);
}
return true;
}
/**
* 逐行选择主元
* @param array
* @param colNum
* @param i 第i行的主元
* @return 主元所在的列
*/
int selectPivotalElement(double array[], int colNum, int i, int arrayLength) {
//返回的所选择的主元所在列的索引
int pivotColIndex = 0 ;
//中间变量
double max = 0;
for (int j = i * colNum + i; j < arrayLength; j += colNum) {
if (fabs(max) < fabs(array[j])) {
max = array[j];
pivotColIndex = j;
}
}
//返回主元所在的行
return pivotColIndex / colNum;
}
/**
* 交换两行
*
* @param array
* @param dist 目标行
* @param src 原来的行
* @param colNum 行向量的长度
*/
void swapCol(double array[], int dist, int src, int colNum) {
int distance = (dist - src) * colNum;
for (int j = src * colNum; j < (src + 1) * colNum; j++) {
double temp = array[j];
array[j] = array[j + distance];
array[j + distance] = temp;
}
}
/**
* 逐行消元
* @param array
* @param i 标示第 i 行的主元
* @param colNum
*/
void emissElement(double array[], int i, int colNum, int arrayLength) {
//矩阵行数
int len = arrayLength / colNum;
//第i+1行开始消元
for (int j = (i + 1); j < len; j++) {
//消元系数
double index = array[j * colNum + i] / array[i * colNum + i];
int ii = i * colNum + i;
for (int k = j * colNum + i; k < (j + 1) * colNum; k++, ii++) {
array[k] = array[k] - array[ii] * index;
}
}
}
/**
* 元素归一化处理
* @param array
* @param i 第 i 行 归一化处理
* @param colNum 矩阵列数 ,每行元素个数
*/
void elementBack(double array[], int i, int colNum, int arrayLength) {
//每行的长度
int len = arrayLength / colNum;
double factor = array[i * colNum + i];
for (int j = i * colNum + i; j < (i + 1) * colNum; j++) {
array[j] = array[j] / factor;
}
}
/**
* 回代消元
* @param array
* @param i 第i行之上的开始消除
* @param colNum 矩阵每一行的个数
*/
void backEmiss(double array[], int i, int colNum) {
for (int j = i; j > 0; j--) {
double factor = array[(j - 1) * colNum + i];
array[(j - 1) * colNum + i] = 0;
array[j * colNum - 1] = array[j * colNum - 1] - array[(i + 1) * colNum - 1] * factor;
}
}
/**
* 打印输出信息
* @param array
* @param colNum
*/
void printArrayInfo(double array[], int colNum, int arrayLength) {
for (int i = 0; i < arrayLength; i++) {
printf("%f\t", array[i]);
if ((i + 1) % colNum == 0) {
printf("\n");
}
}
}
3.3.2 Java
package com.hubu.meetingroom.util;
import java.math.BigDecimal;
/**
* @author zry
* @date 2020-11-17 16:11:17
*/
public class GaussUtil {
/**
* 除法精度(除不尽时保留10为小数)
*/
private static final int DIV_SCALE = 10;
/**
* @param array 系数矩阵 增广矩阵
* @param colNum 列向量个数(从第0列开始)
* @return boolean 是否得出结果
*/
public static boolean resolveByGauss(double[] array, int colNum) {
//行数
int rows = array.length / colNum;
//逐行选择主元 第 i 行 ,第i列 的元素(i,i)
for (int i = 0; i < rows; i++) {
//按列选择主元
int pivotRow = selectPivotalElement(array, colNum, i);
//如果主元不是目前所在的i列,则交换
if (pivotRow != i) {
swapCol(array, pivotRow, i, colNum);
}
if (array[i * colNum + i] == 0) {
System.out.println("不含有唯一解");
return false;
}
//消元得到上三角
for (int j = 0; j < colNum; j++) {
emissElement(array, j, colNum);
}
}
//主元素归一化
for (int i = 0; i < rows; i++) {
elementBack(array, i, colNum);
}
//回代消元,从最后一行开始
for (int i = rows - 1; i > 0; i--) {
backEmiss(array, i, colNum);
}
return true;
}
/**
* 回代消元
* @param array
* @param i 第i行之上的开始消除
* @param colNum 矩阵每一行的个数
*/
private static void backEmiss(double[] array, int i, int colNum) {
for (int j = i; j > 0; j--) {
double factor = array[(j - 1) * colNum + i];
array[(j - 1) * colNum + i] = 0;
array[j * colNum - 1] = sub(array[j * colNum - 1] ,array[(i + 1) * colNum - 1] * factor);
}
}
/**
* 元素归一化处理
* @param array
* @param i 第 i 行 归一化处理
* @param colNum 矩阵列数 ,每行元素个数
*/
private static void elementBack(double[] array, int i, int colNum) {
//每行的长度
int len = array.length / colNum;
double factor = array[i * colNum + i];
for (int j = i * colNum + i; j < (i + 1) * colNum; j++) {
array[j] = div(array[j],factor);
}
}
/**
* 逐行消元
*注意浮点数加减乘除的结果不精确,需要使用BigDecimal对运算过程进行处理
* @param array
* @param i 标示第 i 行的主元
* @param colNum
*/
private static void emissElement(double[] array, int i, int colNum) {
//矩阵行数
int len = array.length / colNum;
//第i+1行开始消元
for (int j = (i + 1); j < len; j++) {
//消元系数
double index = div(array[j * colNum + i],array[i * colNum + i]);
int ii = i * colNum + i;
for (int k = j * colNum + i; k < (j + 1) * colNum; k++, ii++) {
array[k] = sub(array[k],array[ii] * index);
}
}
}
/**
* 交换两行
*
* @param array
* @param dist 目标行
* @param src 原来的行
* @param colNum 行向量的长度
*/
private static void swapCol(double[] array, int dist, int src, int colNum) {
int distance = (dist - src) * colNum;
for (int j = src * colNum; j < (src + 1) * colNum; j++) {
double temp = array[j];
array[j] = array[j + distance];
array[j + distance] = temp;
}
}
/**
*
* @param array
* @param colNum
* @param i 第i行的主元
* @return 主元所在的列
*/
private static int selectPivotalElement(double[] array,int colNum,int i) {
//返回的所选择的主元所在列的索引
int pivotColIndex = 0 ;
//中间变量
double max = 0 ;
for (int j = i*colNum + i; j < array.length; j+=colNum) {
if(Math.abs(max)<Math.abs(array[j])){
max = array[j];
pivotColIndex = j ;
}
}
//返回主元所在的行
return pivotColIndex/colNum;
}
/**
* 打印输出信息
* @param array
* @param colNum
*/
public static void printArrayInfo(double[] array,int colNum){
for (int i = 0; i < array.length; i++) {
System.out.print(array[i]+" ");
if((i+1)%colNum==0){
System.out.println();
}
}
}
public static void main(String[] args) {
//float[] array = new float[]{2,3,11,5,2,1,1,5,2,1,2,1,3,2,-3,1,1,3,4,-3};
double[] array = new double[]{1,-3,5,0,2,-1,-3,11,2,1,-3,5};
System.out.println("题中的增广矩阵");
printArrayInfo(array,4);
GaussUtil.resolveByGauss(array,4);
System.out.println("高斯消元后的矩阵");
printArrayInfo(array,4);
}
/** 小数精确减法 */
public static double sub(double d1,double d2)
{
BigDecimal bd1 = BigDecimal.valueOf(d1);
BigDecimal bd2 = BigDecimal.valueOf(d2);
return bd1.subtract(bd2).doubleValue();
}
/** 小数精确除法 */
public static double div(double d1,double d2)
{
BigDecimal bd1 = BigDecimal.valueOf(d1);
BigDecimal bd2 = BigDecimal.valueOf(d2);
/*
* 当除不尽时,以四舍五入的方式(关于除不尽后的值的处理方式有很多种)保留小数点后10位小数
*/
return bd1.divide(bd2, DIV_SCALE, BigDecimal.ROUND_HALF_UP).doubleValue();
}
}
最终求解后,结果如下,与预期结果一致。
4. 总结
-
高斯消元法在程序中的使用需要注意浮点数运算的精度问题,否则可能会造成很大的误差。故需要选择主元之后再进行其他步骤。(消除主元法也称为列主元素消去法)
-
该方法可适用于n元一次方程组的求解。
5. LU分解法
5.1 LU分解法的算法思路
- 将系数矩阵A通过初等行变换将其变为一个上三角矩阵U,然后,将原始矩阵A变为上三角矩阵的过程,对应的变换矩阵为一个下三角矩阵L,即分解为下三角矩阵 L 和一个上三角矩阵 U 的乘积,将Ax=b转换为LUx=b
- 计算Ly=b
- 计算Ux=y
- 回带消元
5.2 代码实现
以下代码为C++实现的代码:
#include <iostream>
#include <Windows.h>
using namespace std;
double **init_Matrix(int r, int c) {
double **p = new double* [r];
int d = c + 1;
for (int i = 0; i < r; i++) {
p[i] = new double[d];
memset(p[i], 0, sizeof(double) * d);
}
cout << "请输入线性方程组对应的增广矩阵:" << endl;
for (int i = 0; i < r; i++) {
for (int j = 0; j < d; j++) {
cin >> p[i][j];
}
}
return p;
}
//直接用A来储存LU和方程组右边的常数项 进行LU分解时 右边常数项不做处理 即最后一列全部不处理
void LU_Position(double **a, int r, int c) {
double num1 = 0, num2 = 0;
for (int i = 0; i < r; i++) {
if (i == 0) {
for (int j = 1; j < c; j++) {
a[j][i] = a[j][i] / a[0][0];
}
} else {
for (int j = i; j < c; j++) {
num1 = 0;
for (int k = 0; k < i; k++) {
num1 += a[i][k] * a[k][j];
}
a[i][j] = a[i][j] - num1;
}
for (int j = i + 1; j < r; j++) {
num2 = 0;
for (int k = 0; k < i; k++) {
num2 += a[j][k] * a[k][i];
}
a[j][i] = (a[j][i] - num2) / a[i][i];
}
}
}
cout << "所得的LU矩阵为:" << endl;
for (int k = 0; k < r; k++) {
for (int n = 0; n < c; n++) {
printf("%f\t", a[k][n]);
}
cout << endl;
}
}
void calculate(double *y, double *x, double **a, int r) {
y[0] = a[0][r];
double sum = 0;
for (int i = 1; i < r; i++) {
sum = 0;
for (int k = 0; k < i; k++) {
sum += a[i][k] * y[k];
}
y[i] = a[i][r] - sum;
}
cout << "所求的向量Y为:" << endl;
for (int i = 0; i < r; i++) {
printf("%f\t", y[i]);
}
cout << endl;
for (int i = r - 1; i >= 0; i--) {
sum = 0;
for (int j = i + 1; j < r; j++) {
sum += a[i][j] * x[j];
}
x[i] = (y[i] - sum) / a[i][i];
}
cout << "所求线性方程组的解向量X为:" << endl;
for (int i = 0; i < r; i++) {
printf("%f\t", x[i]);
}
cout << endl;
}
void LU_position_main() {
cout << "输入矩阵的行列:" << endl;
int i = 0, j = 0;
cin >> i >> j;
double **p = init_Matrix(i, j);
LU_Position(p, i, j);
double *a = new double[i];
memset(a, 0, sizeof(double) * i);
double *b = new double[i];
memset(b, 0, sizeof(double) * i);
calculate(a, b, p, i);
delete[]a;
delete[]b;
for (int i = 0; i < j; i++) {
delete[]p[i];
}
delete[]p;
}
int main(void) {
LU_position_main();
system("pause");
return 0;
}
最终结果如下,与预期相符:
你知道的越多,你不知道的越多,欢迎访问我的blog:http://www.codinglemon.cn/