向量与矩阵
向量是一个平面的数字列表,比如空间中的一个方向或一组数量。矩阵是由行组成的列表。本页介绍点积与叉积、矩阵乘法、行列式与逆矩阵、一次求解多个线性方程,以及线性代数的数值工具:特征值,以及 QR、LU 和奇异值分解。
线性代数课程作业、几何与变换,或者任何由直线方程联系起来的多个未知数问题,都可以参考本页。向量和矩阵就是普通的列表,所以列表与统计中的所有内容(如按索引取值)同样适用。
向量
dot([1, 2, 3], [4, 5, 6]) 结果为 32dot([3, 4], [4, -3]) 结果为 0cross([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)) 结果为 5dot 把对应位置的元素相乘,再把结果相加。点积为零表示两个向量互相垂直,[3, 4] 和 [4, -3] 就是这样。cross 给出一个同时垂直于两个输入向量的向量:x 方向叉乘 y 方向得到 z 方向。向量的长度是它与自身点积的平方根。
dot 需要两个长度相同的向量,cross 需要两个各含三个元素的向量。要对向量做加法或缩放,请使用普通的列表运算:[1, 2] + [3, 4] 是 [4, 6],2 * [3, 4] 是 [6, 8]。
书写矩阵与矩阵乘法
把矩阵写成由行组成的列表,每一行放在各自的方括号里:
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) 是 n 行 n 列的单位矩阵,任何矩阵与它相乘都保持不变。
两个矩阵之间的 * 表示矩阵乘法,要求左边矩阵的列数等于右边矩阵的行数。要用矩阵乘以向量,请把向量写成列的形式,每行一个元素:
# 将点 (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]。
行列式、逆矩阵、迹与秩
M = [[2, 1], [5, 3]]det(M) 结果为 1inverse(M) 结果为 [[3, -1], [-5, 2]]M * inverse(M) 结果为 [[1, 0], [0, 1]]trace(M) 结果为 5inverse([[2, 1], [1, 3]]) 结果为 [[0.6, -0.2], [-0.2, 0.4]]det([[1, 2], [2, 4]]) 结果为 0rank([[1, 2, 3], [2, 4, 6]]) 结果为 1det、inverse 和 trace(对角线元素之和)需要方阵。行列式为零表示矩阵是奇异的:它的各行不是线性无关的,因此没有逆矩阵,inverse([[1, 2], [2, 4]]) 会报错。rank 计算有多少行线性无关,也接受非方阵。在最后一行中,第二行是第一行的两倍,所以秩为 1。
这四个函数使用精确分数而不是小数,所以 M * inverse(M) 得到的恰好是单位矩阵。
求解线性方程组
假设三杯咖啡和两杯茶共 £13.50,一杯咖啡和四杯茶共 £11.50。每个方程占一行,按固定的列顺序(咖啡、茶)写出每种饮品的数量,再把总价写成对应的列表:
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。
特征值
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
每种分解都返回一个矩阵列表。用索引取出各个部分:
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。存储的值保留了全部位数,所以比较分解结果时请目测或查看差值,而不要用 ==。
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。
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) |
n 行 n 列的单位矩阵 |
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 是平面列表;适用于任何矩形矩阵 |
对于非方阵,qr 和 svd 返回精简形式的因子:对于 3 行 2 列的矩阵,Q 也是 3 行 2 列,而不是 3 行 3 列。
算法与限制
det、inverse、rank 和 solve_system 使用精确分数计算,但受计算器对数字大小的限制。eigenvalues、qr、lu 和 svd 使用二进制浮点运算,所以结果是非常接近的近似值。它们的迭代在 10,000 步后停止。
各行几乎线性相关的矩阵(如 [[1, 1], [1, 1.0000001]])称为病态矩阵。用它们计算时,微小的舍入误差会被放大,因此这类矩阵的分解和特征值可能会损失精度,极端情况下还可能超出数值限制而报错。
完整规则请参阅高等数学:功能与限制参考页。
综合运用
市场摊位的三张收据列出了买了几个苹果、香蕉和橙子以及付款总额,但没有列出单价:
# 每行:苹果、香蕉、橙子购物篮 = [[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。