跳到主要内容
Varlig 指南
Varlig 首页

向量与矩阵

向量是一个平面的数字列表,比如空间中的一个方向或一组数量。矩阵是由行组成的列表。本页介绍点积与叉积、矩阵乘法、行列式与逆矩阵、一次求解多个线性方程,以及线性代数的数值工具:特征值,以及 QR、LU 和奇异值分解。

线性代数课程作业、几何与变换,或者任何由直线方程联系起来的多个未知数问题,都可以参考本页。向量和矩阵就是普通的列表,所以列表与统计中的所有内容(如按索引取值)同样适用。

向量

calc
dot([1, 2, 3], [4, 5, 6]) 结果为 32
dot([3, 4], [4, -3]) 结果为 0
cross([1, 0, 0], [0, 1, 0]) 结果为 [0, 0, 1]
cross([1, 2, 3], [4, 5, 6]) 结果为 [-3, 6, -3]
v = [3, 4]
sqrt(dot(v, v)) 结果为 5

dot 把对应位置的元素相乘,再把结果相加。点积为零表示两个向量互相垂直,[3, 4][4, -3] 就是这样。cross 给出一个同时垂直于两个输入向量的向量:x 方向叉乘 y 方向得到 z 方向。向量的长度是它与自身点积的平方根。

dot 需要两个长度相同的向量,cross 需要两个各含三个元素的向量。要对向量做加法或缩放,请使用普通的列表运算:[1, 2] + [3, 4][4, 6]2 * [3, 4][6, 8]

书写矩阵与矩阵乘法

把矩阵写成由行组成的列表,每一行放在各自的方括号里:

calc
shape([[1, 2, 3], [4, 5, 6]]) 结果为 [2, 3]
transpose([[1, 2, 3], [4, 5, 6]]) 结果为 [[1, 4], [2, 5], [3, 6]]
identity(3) 结果为 [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
[[1, 2], [3, 4]] * [[5, 6], [7, 8]] 结果为 [[19, 22], [43, 50]]

shape 给出 [行数, 列数]transpose 交换行和列。identity(n)nn 列的单位矩阵,任何矩阵与它相乘都保持不变。

两个矩阵之间的 * 表示矩阵乘法,要求左边矩阵的列数等于右边矩阵的行数。要用矩阵乘以向量,请把向量写成列的形式,每行一个元素:

calc
# 将点 (3, 1) 逆时针旋转 90°
旋转 = [[0, -1], [1, 0]]
旋转 * [[3], [1]] 结果为 [[-1], [3]]
旋转 * 旋转 结果为 [[-1, 0], [0, -1]]

旋转两次 90° 就是转半圈,所以 旋转 * 旋转 会使两个符号都翻转。矩阵右边不接受 [3, 1] 这样的平面列表。如果名称中保存的是平面列表,可以用 transpose([v]) 把它变成列。

形状相同的矩阵可以相加和相减,也可以像其他列表一样乘以或除以一个数。

所有矩阵运算都遵循以下几条规则:

  • 每一行的元素个数必须相同。
  • 元素必须是普通数字,不接受单位和金额。
  • 矩阵最多 32 行、32 列。
  • 没有分号简写。请写 [[1, 2], [3, 4]],而不是 [1, 2; 3, 4]

行列式、逆矩阵、迹与秩

calc
M = [[2, 1], [5, 3]]
det(M) 结果为 1
inverse(M) 结果为 [[3, -1], [-5, 2]]
M * inverse(M) 结果为 [[1, 0], [0, 1]]
trace(M) 结果为 5
inverse([[2, 1], [1, 3]]) 结果为 [[0.6, -0.2], [-0.2, 0.4]]
det([[1, 2], [2, 4]]) 结果为 0
rank([[1, 2, 3], [2, 4, 6]]) 结果为 1

detinversetrace(对角线元素之和)需要方阵。行列式为零表示矩阵是奇异的:它的各行不是线性无关的,因此没有逆矩阵,inverse([[1, 2], [2, 4]]) 会报错。rank 计算有多少行线性无关,也接受非方阵。在最后一行中,第二行是第一行的两倍,所以秩为 1。

这四个函数使用精确分数而不是小数,所以 M * inverse(M) 得到的恰好是单位矩阵。

求解线性方程组

假设三杯咖啡和两杯茶共 £13.50,一杯咖啡和四杯茶共 £11.50。每个方程占一行,按固定的列顺序(咖啡、茶)写出每种饮品的数量,再把总价写成对应的列表:

calc
solve_system([[3, 2], [1, 4]], [13.5, 11.5]) 结果为 [3.1, 2.1]

结果的顺序与列的顺序相同:一杯咖啡 £3.10,一杯茶 £2.10。请省略货币符号,因为矩阵元素必须是普通数字。

solve_system(A, b) 求解 A * x = b 中的 x。它需要一个方阵(每个未知数一行),以及一个长度相同、由等号右边的值组成的平面列表。它使用精确分数,所以结果没有舍入误差。

如果这些方程无法确定唯一的解,你会看到一条错误,提示方程组没有唯一解。例如,solve_system([[1, 2], [2, 4]], [3, 6]) 会失败,因为第二个方程是第一个方程的两倍。solve_system 只能处理直线(线性)方程;对于 x^2 = 2 这样的方程,请使用求和、解方程与微积分中介绍的 solve

特征值

calc
eigenvalues([[2, 1], [1, 2]]) 结果为 [1, 3]
eigenvalues([[4, 1], [2, 3]]) 结果为 [5, 2]
eigenvalues([[0, -1], [1, 0]]) 结果为 [i, -i]

eigenvalues 接受一个方阵,返回一个平面列表。90° 旋转不会让任何方向保持不变,所以它的特征值是一对复数 i-i;请参阅复数。不要依赖列表中值的顺序。

矩阵分解:QR、LU 与 SVD

每种分解都返回一个矩阵列表。用索引取出各个部分:

calc
A = [[3, 0], [4, 5]]
因子 = qr(A)
q = 因子[0] 结果为 [[0.6, -0.8], [0.8, 0.6]]
r = 因子[1] 结果为 [[5, 4], [0, 3]]
q * r 结果为 [[3, 0], [4, 5]]

把各个因子重新相乘会再次得到 A,因此你可以一眼检查分解是否正确。二进制浮点运算会留下极小的舍入残差,约为 10⁻¹⁵,Varlig 在列表和矩阵中把它们显示为 0。存储的值保留了全部位数,所以比较分解结果时请目测或查看差值,而不要用 ==

calc
lu([[2, 1], [4, 3]]) 结果为 [[[0, 1], [1, 0]], [[1, 0], [0.5, 1]], [[4, 3], [0, -0.5]]]

lu 返回 [P, L, U],其中 P 记录了行交换,P * A 等于 L * U。这里交换了行,以便先使用较大的数 4。

calc
B = [[3, 1], [4, 7]]
f = svd(B)
u = f[0]
s = f[1]
vt = f[2]
u * [[s[0], 0], [0, s[1]]] * vt 结果为 [[3, 1], [4, 7]]
svd([[3, 4]]) 结果为 [[[1]], [5], [[0.6, 0.8]]]

svd 返回 [U, s, Vt],其中 s 是由奇异值组成的平面列表,从大到小排列。教科书中写作 B 等于 U * diag(s) * Vt。没有 diag 函数,所以请自己构建对角矩阵,如 [[s[0], 0], [0, s[1]]]

函数 需要什么、返回什么
identity(n) nn 列的单位矩阵
shape(A) [行数, 列数]
transpose(A) 交换行和列后的矩阵
det(A) 方阵的行列式
inverse(A) 行列式不为零的方阵的逆矩阵
trace(A) 方阵对角线元素之和
rank(A) 线性无关的行数;适用于任何矩形矩阵
solve_system(A,b) 对于有唯一解的方阵,求 A * x = b 的解
eigenvalues(A) 方阵的特征值,可能是复数,顺序不固定
qr(A) [Q, R]Q * R 近似等于 A;适用于任何矩形矩阵
lu(A) [P, L, U]P * A 近似等于 L * U;适用于方阵
svd(A) [U, s, Vt],其中 s 是平面列表;适用于任何矩形矩阵

对于非方阵,qrsvd 返回精简形式的因子:对于 3 行 2 列的矩阵,Q 也是 3 行 2 列,而不是 3 行 3 列。

算法与限制

detinverseranksolve_system 使用精确分数计算,但受计算器对数字大小的限制。eigenvaluesqrlusvd 使用二进制浮点运算,所以结果是非常接近的近似值。它们的迭代在 10,000 步后停止。

各行几乎线性相关的矩阵(如 [[1, 1], [1, 1.0000001]])称为病态矩阵。用它们计算时,微小的舍入误差会被放大,因此这类矩阵的分解和特征值可能会损失精度,极端情况下还可能超出数值限制而报错。

完整规则请参阅高等数学:功能与限制参考页。

综合运用

市场摊位的三张收据列出了买了几个苹果、香蕉和橙子以及付款总额,但没有列出单价:

calc
# 每行:苹果、香蕉、橙子
购物篮 = [[2, 3, 1], [1, 2, 3], [4, 1, 2]]
实付 = [2.15, 2.70, 3.05]
det(购物篮) 结果为 25
单价 = solve_system(购物篮, 实付) 结果为 [0.4, 0.25, 0.6]
# 用每张收据核对单价
购物篮 * transpose([单价]) 结果为 [[2.15], [2.7], [3.05]]
# 5 个苹果、2 根香蕉和 4 个橙子要多少钱?
dot([5, 2, 4], 单价) 结果为 4.9

行列式不为零,说明这些收据足以确定唯一一组单价:苹果 40 便士,香蕉 25 便士,橙子 60 便士。把购物篮乘以写成列的单价,就能还原各张收据的总额,而 dot 算出新的一篮要 £4.90。