高斯消元法求解多元线性方程组

高斯消元法求解多元线性方程组

用高斯消元法求解多元线性方程组

多元线性方程组

形式为:

\[\begin{cases}

a_{11}x_1+a_{12}x_2+\dots+a_{1n}x_n=b_1 \\

a_{21}x_1+a_{22}x_2+\dots+a_{2n}x_n=b_2 \\

\vdots \\

a_{n1}x_1+a_{n2}x_2+\dots+a_{nn}x_n=b_n \\

\end{cases}

\]

求解 \(x_1,x_2,\dots,x_n\).

解的可能一共有三种:

\[\begin{cases}

1.有唯一解 \\

2.无解 \\

3.有无穷多组解 \\

\end{cases}

\]

解法:高斯消元法

初等行列变换操作:

(以下操作是对原方程组的等价变形)

把某一行乘一个非零的数;(等式两边同时乘一个非0的数)

交换某两行;(交换某两个方程的位置)

把某行的若干倍加到另一行上去。(某个方程乘若干倍加到另一个方程上去)

步骤:

枚举每一列:

找到剩下行中当前这一列绝对值最大的一行;

操作2:将这一行换到最上面去;

操作1:将该行的第一个数变成1,即每个系数都等比缩小(或放大);

操作3:用这一行将下面所有行的当前列消成0。

举例:

1.有唯一解

\[\begin{cases}

-x_1-x_2+2x_3=7 \\

2x_1+x_2-3x_3=-9 \\

x_1+2x_2-x_3=-6 \\

\end{cases}

\]

转化为矩阵:

-1

-1

2

7

2

1

-3

-9

1

2

-1

-6

对于第 1 列:

先找到 \(2\) 最大;

将第 \(2\) 行换到第 \(1\) 行;

2

1

-3

-9

-1

-1

2

7

1

2

-1

-6

使第一个数变成 \(1\),同时除以 \(2\);

1

0.5

-1.5

-4.5

-1

-1

2

7

1

2

-1

-6

用这一行将下面所有行的当前列消成 \(0\):第 \(2\) 行减第 \(1\) 行的 \(-1\) 倍;第3行减第1行的 \(1\) 倍。

1

0.5

-1.5

-4.5

0

-0.5

0.5

2.5

0

1.5

0.5

-1.5

对于第 2 列:

从第 2 行开始找,第 2 列绝对值最大的是1.5;

将第 \(3\) 行换到第 2 行;

1

0.5

-1.5

-4.5

0

1.5

0.5

-1.5

0

-0.5

0.5

2.5

使第一个数变成 \(1\),同时除以 \(1.5\);

1

0.5

-1.5

-4.5

0

1

1/3

-1

0

-0.5

0.5

2.5

用这一行将下面所有行的当前列消成 \(0\):第 \(3\) 行减第 \(1\) 行的 \(-0.5\) 倍。

1

0.5

-1.5

-4.5

0

1

1/3

-1

0

0

2/3

2

对于第 3 列:

从第 3 行开始找,第 3 列绝对值最大 的是 \(\frac{2}{3}\);

将第 3 行换到第 3 行,相当于不变;

使第 3 行第一个数变成 \(1\),同时除以 \(\frac{2}{3}\);

1

0.5

-1.5

-4.5

0

1

1/3

-1

0

0

1

3

没有下一行,跳过此步。

所有有唯一解的方程组,经过此 \(4\) 步的循环操作之后,行数 \(r\) 会枚举完每一行,最终矩阵会变成上面的三角形的形式。也就是将方程组化简为这样:

\[\begin{cases}

x_1+0.5x_2-1.5x_3=-4.5 \\

\qquad \qquad x_2+\frac{1}{3}x_3=-1 \\

\qquad \qquad \qquad \quad x_3=3 \\

\end{cases}

\]

我们就可以倒着一步步推出每个解。最终每个解就是矩阵每行的最后一个数字,即 \(x_i=a[i][n+1]\)。

2.无解

\[\begin{cases}

x_1=0 \\

x_3=1 \\

x_3=0 \\

\end{cases}

\]

其对应的矩阵表示为:

1

0

0

0

0

0

1

1

0

0

1

0

枚举到第 2 列时,从第 2 行看起,会发现绝对值最大的是0;

于是直接从第 3 列的第 2 行看起,找到绝对值最大的第 2 行的 1,换到第 2 行不必交换了。然后把下面的全部变成 \(0\),也就是用第 3 行减去第 2 行。结果为:

1

0

0

0

0

0

1

1

0

0

0

-1

也就等价于:

\[\begin{cases}

x_1=0 \\

x_3=1 \\

0=-1 \\

\end{cases}

\]

由此我们看出,经过等价变形,我们得出了一个显然不成立的式子 \(0=-1\),所以这个方程组无解。

也就是说,当出现 \(0=非零数\) 这样形式的式子的时候,就是方程组无解的时候。

3.有无穷组解

\[\begin{cases}

x_1+x_3=2 \\

x_1+x_2=3 \\

2x_1+2x_2=6 \\

\end{cases}

\]

首先我们会发现,(3)式是由(2)式乘2得到,相当于这个方程组只有两个式子,却有3个未知数,显然有无穷组解。

我们看看程序运行过程是怎样的:

1

0

1

2

1

1

0

3

2

2

0

6

对于第 1 列:

首先交换1、3行,将 \(x_1\) 的系数变为 \(1\),变成: \(\begin{cases} x_1+x_2=3 \\ x_1+x_2=3 \\ x_1+x_3=2 \\ \end{cases}\)

然后将2、3行的 \(x_1\) 项消掉,即(2)式减(1)式,(3)式减((1)式,结果为:\(\begin{cases} x_1+x_2=3 \\ 0=0 \\ -x_2+x_3=-1 \\ \end{cases}\),即:

1

1

0

3

0

0

0

0

0

-1

1

-1

对于第 2 列:

首先交换2、3行,将 \(x_2\) 的系数变为 \(1\),变成:\(\begin{cases} x_1+x_2=3 \\ x_2-x_3=1 \\ 0=0 \\ \end{cases}\);下面的 \(x_2\) 项系数全部为 \(0\),因此无需改动。即:

1

1

0

3

0

1

-1

1

0

0

0

0

枚举的行数 \(r\) 没有枚举到最后一行就已经完毕,因此属于非正常情况,又没有出现 0=非零数 这样的无解情况,因此是无穷组解。

代码

#include

#include

using namespace std;

const int N = 105;

const double eps = 1e-6;

int n;

double a[N][N];

int gauss() { //高斯消元法

int r, c, i, j; //r为行,c为列

for (r = 1, c = 1; c <= n; c++) {

int t = r;

for (i = r; i <= n; i++) //循环找到当前列绝对值最大的行t

if (fabs(a[i][c]) > fabs(a[t][c])){

t = i;

break;

}

if (fabs(a[t][c]) < eps) //为0

continue;

if (t != r) //有更大的才交换

for (i = c; i <= n + 1; i++) //交换到当前行,从c列开始交换是因为前面都是0

swap(a[r][i], a[t][i]);

if (a[r][c] != 1) //不是1才需要变成1

for (i = n + 1; i >= c; i--) //将第c列的数字变为1,第一个数字要最后变

a[r][i] /= a[r][c];

for (i = r + 1; i <= n; i++) //将下面每行的第c列变成0

if (fabs(a[i][c]) > eps) //不是0的才变,是0就不变了

for (j = n + 1; j >= c; j--)

a[i][j] -= a[r][j] * a[i][c];

r++;

}

if (r <= n) { //说明不是正常情况,正常r=n+1

for (i = r; i <= n; i++)

if (fabs(a[i][n + 1]) > eps)

return -1; //无解

return 2; //无穷组解

}

for (i = n; i > 0; i--) //倒着一步步推出每个解

for (j = i + 1; j <= n; j++)

a[i][n + 1] -= a[i][j] * a[j][n + 1];

return 1; //有唯一解

}

int main() {

int i, j;

scanf("%d", &n);

for (i = 1; i <= n; i++)

for (j = 1; j <= n + 1; j++) //输入的是一个n*(n+1)的矩阵

scanf("%lf", &a[i][j]);

int t = gauss();

if (t == -1) { //无解

puts("No solution");

} else if (t == 2) { //有无穷组解

puts("Infinite group solutions");

} else { //有唯一解

for (i = 1; i <= n; i++)

printf("%.2lf\n", a[i][n + 1]);

}

return 0;

}

变式

1.解异或线性方程组

形式为:

\[\begin{cases}

a_{11}x_1 \land a_{12}x_2\land \dots \land a_{1n}x_n=b_1 \\

a_{21}x_1 \land a_{22}x_2 \land \dots \land a_{2n}x_n=b_2 \\

\vdots \\

a_{n1}x_1 \land a_{n2}x_2 \land \dots \land a_{nn}x_n=b_n \\

\end{cases}

\]

其中 ^ 表示异或运算(XOR),方程组的系数 \(a\) 和常数 \(b\) 均为 \(0\) 或 \(1\),未知数可能的值也为 \(0\) 或 \(1\)。

求解: \(x_1,x_2,\dots,x_n\).

思路

与一般的高斯消元法相同,只是稍作改动。

不再需要 double类型,使用 bool 类型即可;

不再需要等比例缩小至系数为 \(1\),因为系数不是 \(0\) 就是 \(1\);

加减消元可以用 ^ 运算来代替,因为异或运算俗称不进位的加法运算。

乘法运算可以用 & 运算来代替,众所周知位运算是最快的运算。

举例

\[\begin{cases}

x_1 \land x_2=1 \\

x_2 \land x_3=0 \\

x_1=1 \\

\end{cases}

\]

表示为:

1

1

0

1

0

1

1

0

1

0

0

1

将第 \(3\) 行的第 \(1\) 列变成 \(0\),就是将第 \(1\) 行与第 \(3\) 行的系数做异或运算再放入第 \(3\) 行,变成:\(\begin{cases} x_1 \land x_2=1 \\ x_2 \land x_3=0 \\ x_2=0 \\ \end{cases}\)

1

1

0

1

0

1

1

0

0

1

0

0

将第 \(3\) 行的第 \(2\) 列变成 \(0\),就是将第 \(2\) 行与第 \(3\) 行的系数做异或运算再放入第 \(3\) 行,变成:\(\begin{cases} x_1 \land x_2=1 \\ x_2 \land x_3=0 \\ x_3=0 \\ \end{cases}\)

1

1

0

1

0

1

1

0

0

0

1

0

最后倒推回去即可。这里注意:异或运算的逆运算还是异或运算。因此要求 \(x_2\),只需求 \(x_2=0 \land x_3=0 \land 0=0\) 即可。

最终可得到,此方程组的解为:\(\begin{cases} x_1=1 \\ x_2=0 \\ x_3=0 \\ \end{cases}\)

代码

#include

using namespace std;

const int N = 105;

int n;

bool a[N][N];

int gauss() { //高斯消元法

int r, c, i, j; //r为行,c为列

for (r = 1, c = 1; c <= n; c++) {

int t = r;

for (i = r; i <= n; i++) //循环找到当前列绝对值最大的行t

if (a[i][c]){ //是1就可以

t = i;

break;

}

if (!a[t][c]) continue; //为0

if (t != r) //有更大的才交换

for (i = c; i <= n + 1; i++) //交换到当前行,从c列开始交换是因为前面都是0

swap(a[r][i], a[t][i]);

for (i = r + 1; i <= n; i++) //将下面每行的第c列变成0

if (a[i][c]) //是1才需要变

for (j = n + 1; j >= c; j--)

a[i][j] ^= a[r][j];

r++;

}

if (r <= n) { //说明不是正常情况,正常r=n+1

for (i = r; i <= n; i++)

if (a[i][n + 1])

return -1; //无解

return 2; //多组解

}

for (i = n; i > 0; i--) //倒着一步步推出每个解

for (j = i + 1; j <= n; j++)

a[i][n + 1] ^= a[i][j] & a[j][n + 1];

return 1; //有唯一解

}

int main() {

int i, j;

scanf("%d", &n);

for (i = 1; i <= n; i++)

for (j = 1; j <= n + 1; j++) //输入的是一个n*(n+1)的矩阵

cin >> a[i][j]; //此处注意,如果想用scanf读入,就不能使用bool数组了,应换成int

int t = gauss();

if (t == -1) { //无解

puts("No solution");

} else if (t == 2) { //有多组解

puts("Multiple sets of solutions");

} else { //有唯一解

for (i = 1; i <= n; i++)

printf("%d\n", a[i][n + 1]);

}

return 0;

}

相关推荐