第 8 章 线性方程组与高斯消元
学习目标
- 理解线性方程组,能把应用题翻译成方程组
- 掌握方程组的矩阵形式
- 掌握三种行初等变换,理解为什么它们不改变解
- 会用高斯消元与回代求解方程组
- 理解解的三种情况:唯一解、无解、无穷多解
- 会用 NumPy 与 PyTorch 解方程组,并能从零实现高斯消元
8.1 从应用题到方程组
小学的鸡兔同笼:笼子里有鸡和兔,一共 35 个头、94 只脚。问鸡、兔各几只?
设鸡 只、兔 只。「头」给出第一个方程,「脚」给出第二个:
小学的解法是猜或画图;现在用消元:由第一个方程得 ,代入第二个方程:
鸡 23 只、兔 12 只,验算:头 ✓,脚 ✓。
这类方程叫线性方程:未知数都只有一次方,没有 、、。线性方程组是一组线性方程放在一起,求一组数同时满足所有方程。
8.2 方程组的矩阵形式
把系数、未知数、右端分成三块:
验证一下:矩阵乘法的定义就是「列的线性组合」,,按分量展开恰好是原来的两个方程。所以:
是任何线性方程组的统一写法。 叫系数矩阵, 叫未知向量, 叫右端向量。
一般地, 个方程、 个未知数写成 。
8.3 行初等变换:换一种写法,不解变
解方程组靠「变形」:小学代入法,本质是把方程组换成另一个有同样解的方程组。高斯消元用三种基本变形,叫行初等变换:
- 交换两行;
- 某一行乘以非零常数;
- 某一行加上另一行的倍数。
为什么它们不改变解?三条都可以反过来做(逆变换存在),所以新旧方程组的解集完全一样。特别是第三条——「第 2 行减去第 1 行」——对应小学的「两式相减消元」。
为了少写字,把方程组写成增广矩阵:系数矩阵右边加一列 ,用竖线隔开:
它表示方程组
(这个方程组的解是 ,下面一步步把它消出来。)
8.4 高斯消元:把矩阵变成「阶梯」
高斯消元的目标:只用行初等变换,把增广矩阵的左边变成行阶梯形——每行第一个非零元素(叫主元)在上一行主元的右边,主元以下全为 0,像楼梯一样。
对上面的增广矩阵,逐步做:
第 1 步:用第 1 行消掉下面两行的 。
第 2 步:让第 2 行的主元变成 1:
第 3 步:用第 2 行消掉第 3 行的 :
阶梯形完成。最后一行读出 :
回代:从下往上依次代回。第 2 行给出 ;第 1 行:
解是 ,和开头说的一致。
用 NumPy 一行验证:
import numpy as np
A = np.array([[1.0, 1.0, 1.0],
[1.0, -1.0, 1.0],
[2.0, 1.0, -1.0]])
b = np.array([6.0, 2.0, 1.0])
x = np.linalg.solve(A, b)
print("解 x =", x)
print("验证 A@x =", A @ x)运行输出:
解 x = [1. 2. 3.]
验证 A@x = [6. 2. 1.]PyTorch 同样可以:
import torch
A = torch.tensor([[1.0, 1.0, 1.0],
[1.0, -1.0, 1.0],
[2.0, 1.0, -1.0]])
b = torch.tensor([6.0, 2.0, 1.0])
x = torch.linalg.solve(A, b)
print("torch 解 x =", x)运行输出:
torch 解 x = tensor([1., 2., 3.])8.5 解的三种情况

二维里两条直线的三种关系,对应方程组的三种解:
import matplotlib.pyplot as plt
from matplotlib import font_manager
for f in font_manager.findSystemFonts():
if any(k in f for k in ("NotoSansCJK", "NotoSansSC", "wqy", "SimHei", "msyh", "PingFang")):
font_manager.fontManager.addfont(f)
plt.rcParams["font.sans-serif"] = ["Noto Sans CJK SC", "WenQuanYi Zen Hei", "SimHei", "Microsoft YaHei", "PingFang SC"]
plt.rcParams["axes.unicode_minus"] = False
import numpy as np
fig, axes = plt.subplots(1, 3, figsize=(13, 4))
def lines(ax, eq1, eq2, title):
x = np.linspace(-1, 3, 100)
a1, b1, c1 = eq1
a2, b2, c2 = eq2
ax.plot(x, (c1 - a1 * x) / b1, color="tab:red", label=f"{a1}x + {b1}y = {c1}")
ax.plot(x, (c2 - a2 * x) / b2, color="tab:blue", label=f"{a2}x + {b2}y = {c2}")
ax.set_xlim(-1, 3)
ax.set_ylim(-1, 3)
ax.set_aspect("equal")
ax.grid(True, linestyle=":", alpha=0.5)
ax.axhline(0, color="black", linewidth=1)
ax.axvline(0, color="black", linewidth=1)
ax.set_title(title)
ax.legend(fontsize=8)
# 唯一解:交于 (2, 1)
lines(axes[0], (1, 1, 3), (1, -1, 1), "唯一解:两条线相交")
axes[0].scatter([2], [1], color="black", zorder=3)
# 无解:平行
lines(axes[1], (1, 1, 1), (1, 1, 3), "无解:两条线平行")
# 无穷多解:重合
lines(axes[2], (1, 1, 1), (2, 2, 2), "无穷多解:两条线重合")
fig.savefig("book/public/figs/fig08-solutions.png", dpi=120, bbox_inches="tight")
plt.close(fig)- 唯一解:两条直线交于一点。刚才的方程组就是这种:恰好一组 满足所有方程。
- 无解:方程互相矛盾,比如
两式相减得 ,不可能。增广矩阵消元后会出现一行 [0 0 | 2],即「」。
- 无穷多解:方程重复或成比例,比如
第二个方程只是第一个的 2 倍,两个方程其实是同一条直线,直线上每一个点都是解。这时未知数多于有效方程,出现自由变量(可以取任意值,其余变量由它决定)。
判别规律:消元到行阶梯形后,
- 出现
0 = 非零数的矛盾行 ⟹ 无解; - 没有矛盾行,且主元个数 = 未知数个数 ⟹ 唯一解;
- 没有矛盾行,但主元个数 < 未知数个数 ⟹ 无穷多解。
8.6 从零实现高斯消元
把 8.4 的步骤写成代码,顺便处理一个实际问题:除以主元时,如果主元恰好是 0(或非常小),换一行再除——这叫部分选主元,能显著减小浮点误差。
import numpy as np
def gauss_solve(A, b):
"""用高斯消元 + 回代求解 A x = b(A 为方阵,假设唯一解)"""
M = np.array(A, dtype=float)
rhs = np.array(b, dtype=float)
n = M.shape[0]
# 消元:把 M 变成上三角
for col in range(n):
# 部分选主元:在 col..n-1 行里找绝对值最大的元素所在行
pivot = col + np.argmax(np.abs(M[col:, col]))
if M[pivot, col] == 0:
raise ValueError("主元为 0:矩阵不可逆或需要行交换后仍无解")
M[[col, pivot]] = M[[pivot, col]]
rhs[[col, pivot]] = rhs[[pivot, col]]
for row in range(col + 1, n):
factor = M[row, col] / M[col, col]
M[row, col:] -= factor * M[col, col:]
rhs[row] -= factor * rhs[col]
# 回代:从最后一行往上解
x = np.zeros(n)
for row in range(n - 1, -1, -1):
x[row] = (rhs[row] - M[row, row + 1:] @ x[row + 1:]) / M[row, row]
return x
A = np.array([[1.0, 1.0, 1.0],
[1.0, -1.0, 1.0],
[2.0, 1.0, -1.0]])
b = np.array([6.0, 2.0, 1.0])
x = gauss_solve(A, b)
print("自实现高斯消元:", x)
print("与 np.linalg.solve 一致?", np.allclose(x, np.linalg.solve(A, b)))运行输出:
自实现高斯消元: [1. 2. 3.]
与 np.linalg.solve 一致? True代码与手算步骤一一对应:选主元、消去下方、回代。建议你对照 8.4 的手算过程读一遍这段代码。
动手实践
- 把鸡兔同笼方程组写成矩阵形式,用
np.linalg.solve求解并验算。 - 手算方程组 的增广矩阵,用高斯消元求出解,再用代码验证。
- 判断 的解的情况,画出两条直线。
- 用 8.6 的
gauss_solve解一个 4 元方程组(自拟),并与np.linalg.solve对比。 - 故意给
np.linalg.solve一个不可逆矩阵,记录报错信息。
常见错误
| 错误写法/理解 | 原因 |
|---|---|
| 增广矩阵竖线右边放的是系数 | 竖线右边是右端向量 ,左边是系数矩阵 |
| 消元时只改增广矩阵的一边 | 每次行变换必须同时作用于系数与右端,否则解变 |
| 把行乘以 0 当合法变换 | 行变换要求乘非零常数,乘 0 会丢信息、改变解集 |
| 认为 表示「解是 2」 | 0 = 2 是矛盾,表示无解 |
| 主元是 0 直接除 | 除零报错;应交换行(部分选主元)后再消元 |
章末练习
基础
- 解方程组(手算并验证):
- 写出方程组的矩阵形式与增广矩阵。
- 用
np.linalg.solve解上面的方程组并验算。
提高
- 判断下列方程组解的情况,并说明理由:
- 用高斯消元解三元方程组并把每一步行变换写清楚。
挑战
- 证明:三种行初等变换都可逆(即逆变换仍是一种行初等变换),从而不改变解集。
- 三个数,两两之和分别为 。设三个数为 ,列出三个方程并求 。
章末自测
- 方程组 、 的解是?
- A.
- B.
- C.
- D.
- 矩阵形式 中, 叫做?
- A. 右端向量
- B. 系数矩阵
- C. 未知向量
- D. 增广矩阵
- 下列哪个不是行初等变换?
- A. 交换两行
- B. 某行乘以 0
- C. 某行乘以非零常数
- D. 某行加上另一行的倍数
- 消元后出现
[0 0 | 2]表示?- A. 解是 2
- B. 无解
- C. 无穷多解
- D. 解是 0
- 、 的解是?
- A. 唯一解
- B. 无解
- C. 无穷多解
- D.
- 高斯消元的目标是把矩阵变成?
- A. 对角矩阵
- B. 行阶梯形
- C. 单位矩阵
- D. 零矩阵
- 「部分选主元」的作用是?
- A. 让结果更美观
- B. 避免除零并减小浮点误差
- C. 让矩阵变小
- D. 让解变成整数
- 、 的解是?
- A. 唯一解
- B. 无解
- C. 无穷多解
- D. 只有
np.linalg.solve(A, b)要求 是?- A. 任意形状
- B. 方阵
- C. 对称矩阵
- D. 单位矩阵
- 回代是?
- A. 从第一行开始解
- B. 从最后一行开始,自下而上求解
- C. 把解代回原方程
- D. 交换行的操作
