Skip to content

第 8 章 线性方程组与高斯消元

学习目标

  • 理解线性方程组,能把应用题翻译成方程组
  • 掌握方程组的矩阵形式
  • 掌握三种行初等变换,理解为什么它们不改变解
  • 会用高斯消元与回代求解方程组
  • 理解解的三种情况:唯一解、无解、无穷多解
  • 会用 NumPy 与 PyTorch 解方程组,并能从零实现高斯消元

8.1 从应用题到方程组

小学的鸡兔同笼:笼子里有鸡和兔,一共 35 个头、94 只脚。问鸡、兔各几只?

设鸡 只、兔 只。「头」给出第一个方程,「脚」给出第二个:

小学的解法是猜或画图;现在用消元:由第一个方程得 ,代入第二个方程:

鸡 23 只、兔 12 只,验算:头 ✓,脚 ✓。

这类方程叫线性方程:未知数都只有一次方,没有 线性方程组是一组线性方程放在一起,求一组数同时满足所有方程。

8.2 方程组的矩阵形式

把系数、未知数、右端分成三块:

验证一下:矩阵乘法的定义就是「列的线性组合」,,按分量展开恰好是原来的两个方程。所以:

是任何线性方程组的统一写法。系数矩阵,未知向量,右端向量

一般地, 个方程、 个未知数写成

8.3 行初等变换:换一种写法,不解变

解方程组靠「变形」:小学代入法,本质是把方程组换成另一个有同样解的方程组。高斯消元用三种基本变形,叫行初等变换:

  1. 交换两行;
  2. 某一行乘以非零常数;
  3. 某一行加上另一行的倍数

为什么它们不改变解?三条都可以反过来做(逆变换存在),所以新旧方程组的解集完全一样。特别是第三条——「第 2 行减去第 1 行」——对应小学的「两式相减消元」。

为了少写字,把方程组写成增广矩阵:系数矩阵右边加一列 ,用竖线隔开:

它表示方程组

(这个方程组的解是 ,下面一步步把它消出来。)

8.4 高斯消元:把矩阵变成「阶梯」

高斯消元的目标:只用行初等变换,把增广矩阵的左边变成行阶梯形——每行第一个非零元素(叫主元)在上一行主元的右边,主元以下全为 0,像楼梯一样。

对上面的增广矩阵,逐步做:

第 1 步:用第 1 行消掉下面两行的

第 2 步:让第 2 行的主元变成 1:

第 3 步:用第 2 行消掉第 3 行的 :

阶梯形完成。最后一行读出 :

回代:从下往上依次代回。第 2 行给出 ;第 1 行:

解是 ,和开头说的一致。

用 NumPy 一行验证:

python
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)

运行输出:

text
解 x = [1. 2. 3.]
验证 A@x = [6. 2. 1.]

PyTorch 同样可以:

python
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)

运行输出:

text
torch 解 x = tensor([1., 2., 3.])

8.5 解的三种情况

方程组的三种解

二维里两条直线的三种关系,对应方程组的三种解:

python
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)
  1. 唯一解:两条直线交于一点。刚才的方程组就是这种:恰好一组 满足所有方程。
  2. 无解:方程互相矛盾,比如

两式相减得 ,不可能。增广矩阵消元后会出现一行 [0 0 | 2],即「」。

  1. 无穷多解:方程重复或成比例,比如

第二个方程只是第一个的 2 倍,两个方程其实是同一条直线,直线上每一个点都是解。这时未知数多于有效方程,出现自由变量(可以取任意值,其余变量由它决定)。

判别规律:消元到行阶梯形后,

  • 出现 0 = 非零数 的矛盾行 ⟹ 无解;
  • 没有矛盾行,且主元个数 = 未知数个数 ⟹ 唯一解;
  • 没有矛盾行,但主元个数 < 未知数个数 ⟹ 无穷多解。

8.6 从零实现高斯消元

把 8.4 的步骤写成代码,顺便处理一个实际问题:除以主元时,如果主元恰好是 0(或非常小),换一行再除——这叫部分选主元,能显著减小浮点误差。

python
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)))

运行输出:

text
自实现高斯消元: [1. 2. 3.]
与 np.linalg.solve 一致? True

代码与手算步骤一一对应:选主元、消去下方、回代。建议你对照 8.4 的手算过程读一遍这段代码。

动手实践

  1. 把鸡兔同笼方程组写成矩阵形式,用 np.linalg.solve 求解并验算。
  2. 手算方程组 的增广矩阵,用高斯消元求出解,再用代码验证。
  3. 判断 的解的情况,画出两条直线。
  4. 用 8.6 的 gauss_solve 解一个 4 元方程组(自拟),并与 np.linalg.solve 对比。
  5. 故意给 np.linalg.solve 一个不可逆矩阵,记录报错信息。

常见错误

错误写法/理解原因
增广矩阵竖线右边放的是系数竖线右边是右端向量 ,左边是系数矩阵
消元时只改增广矩阵的一边每次行变换必须同时作用于系数与右端,否则解变
把行乘以 0 当合法变换行变换要求乘非零常数,乘 0 会丢信息、改变解集
认为 表示「解是 2」0 = 2 是矛盾,表示无解
主元是 0 直接除除零报错;应交换行(部分选主元)后再消元

章末练习

基础

  1. 解方程组(手算并验证):
  2. 写出方程组的矩阵形式与增广矩阵。
  3. np.linalg.solve 解上面的方程组并验算。

提高

  1. 判断下列方程组解的情况,并说明理由:
  2. 用高斯消元解三元方程组并把每一步行变换写清楚。

挑战

  1. 证明:三种行初等变换都可逆(即逆变换仍是一种行初等变换),从而不改变解集。
  2. 三个数,两两之和分别为 。设三个数为 ,列出三个方程并求

章末自测

  1. 方程组 的解是?
    • A.
    • B.
    • C.
    • D.
  2. 矩阵形式 中, 叫做?
    • A. 右端向量
    • B. 系数矩阵
    • C. 未知向量
    • D. 增广矩阵
  3. 下列哪个不是行初等变换?
    • A. 交换两行
    • B. 某行乘以 0
    • C. 某行乘以非零常数
    • D. 某行加上另一行的倍数
  4. 消元后出现 [0 0 | 2] 表示?
    • A. 解是 2
    • B. 无解
    • C. 无穷多解
    • D. 解是 0
  5. 的解是?
    • A. 唯一解
    • B. 无解
    • C. 无穷多解
    • D.
  6. 高斯消元的目标是把矩阵变成?
    • A. 对角矩阵
    • B. 行阶梯形
    • C. 单位矩阵
    • D. 零矩阵
  7. 「部分选主元」的作用是?
    • A. 让结果更美观
    • B. 避免除零并减小浮点误差
    • C. 让矩阵变小
    • D. 让解变成整数
  8. 的解是?
    • A. 唯一解
    • B. 无解
    • C. 无穷多解
    • D. 只有
  9. np.linalg.solve(A, b) 要求 是?
    • A. 任意形状
    • B. 方阵
    • C. 对称矩阵
    • D. 单位矩阵
  10. 回代是?
    • A. 从第一行开始解
    • B. 从最后一行开始,自下而上求解
    • C. 把解代回原方程
    • D. 交换行的操作