阶段 08 讲义
数学 11h:线代 · 行列式与克拉默法则
编程 9h:字典与集合
第一部分 · 数学
从这一阶段开始,我们从"高数"进入"线性代数"。
线代的对象是方程组,而行列式是解方程组的一把钥匙。先把工具磨好,阶段 9 才开始算矩阵。
1 二阶行列式
问题:解二元一次方程组
$$\begin{cases} a_{11}x + a_{12}y = b_1 \\ a_{21}x + a_{22}y = b_2 \end{cases}$$
用消元法解(第一个方程乘 $a_{22}$,第二个乘 $a_{12}$,相减):
$$(a_{11}a_{22} - a_{12}a_{21})x = b_1a_{22} - a_{12}b_2$$
分母那个东西反复出现,于是数学家给它起了个名字:
$$\begin{vmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{vmatrix} = a_{11}a_{22} - a_{12}a_{21}$$
记忆:主对角线乘积 − 副对角线乘积。
💡 记号:两条竖线是行列式,和矩阵的方括号
[ ]/ 圆括号( )不是一回事。
行列式算出来是一个数;矩阵是一张表。
例子:
$$\begin{vmatrix} 2 & 1 \\ 1 & -3 \end{vmatrix} = 2\times(-3) - 1\times1 = -6-1 = -7$$
2 三阶行列式:萨吕斯法则
$$\begin{vmatrix} a_{11} & a_{12} & a_{13} \\ a_{21} & a_{22} & a_{23} \\ a_{31} & a_{32} & a_{33} \end{vmatrix}$$
萨吕斯法则(对角线法则):
① 三条"主方向"对角线相乘,相加
$$a_{11}a_{22}a_{33} + a_{12}a_{23}a_{31} + a_{13}a_{21}a_{32}$$
② 三条"副方向"对角线相乘,相减
$$-\; a_{13}a_{22}a_{31} - a_{12}a_{21}a_{33} - a_{11}a_{23}a_{32}$$
⚠️ 萨吕斯法则只对二阶和三阶成立!四阶以上不能这样算。
四阶以上要用按行(列)展开,或者直接上计算机。
手算技巧:把前两列抄到右边,画 6 条对角线,
$$\begin{vmatrix} a_{11} & a_{12} & a_{13} \\ a_{21} & a_{22} & a_{23} \\ a_{31} & a_{32} & a_{33} \end{vmatrix} \begin{matrix} a_{11} & a_{12} \\ a_{21} & a_{22} \\ a_{31} & a_{32} \end{matrix}$$
↘ 方向三组相加,↗ 方向三组相减。
2.1 完整例子
计算 $\begin{vmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 10 \end{vmatrix}$
↘ 主方向(相加):
$$1\cdot5\cdot10 + 2\cdot6\cdot7 + 3\cdot4\cdot8 = 50 + 84 + 96 = 230$$
↗ 副方向(相减):
$$3\cdot5\cdot7 + 2\cdot4\cdot10 + 1\cdot6\cdot8 = 105 + 80 + 48 = 233$$
结果:
$$230 - 233 = -3$$
⚠️ 手算最容易错的地方:把 6 条对角线漏掉一条。
建议按顺序数:主方向从左上角开始,依次取三个起点;副方向同理。
算完核对一下,是不是正好 6 项?
3 行列式的性质 ⭐(用来简化计算)
直接按定义算三阶很累。性质才是真正的工具,它们让你不算就能得出结论。
| # | 性质 | 直观理解 |
|---|---|---|
| 1 | $\det(I)=1$ | 单位阵不变形 |
| 2 | 交换两行(列),行列式变号 | 翻转方向 |
| 3 | 某行(列)乘 $k$,行列式乘 $k$ | 拉伸 $k$ 倍 |
| 4 | 两行(列)相同 → 行列式 = 0 | 冗余 |
| 5 | 某行是另一行的倍数 → 行列式 = 0 | 冗余 |
| 6 | 某行加上另一行的 $k$ 倍,行列式不变 | 剪切不改变面积 |
| 7 | $\det(A^T) = \det(A)$ | 行列对等 |
| 8 | $\det(AB) = \det(A)\det(B)$ | 复合变换的伸缩 |
| 9 | 上(下)三角行列式 = 对角线乘积 | 只剩对角 |
3.1 这些性质怎么用
用法一:不算就知道是 0(性质 4、5)
$$\begin{vmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 2 & 4 & 6 \end{vmatrix} = 0 \quad\text{(第 3 行 = 第 1 行 × 2)}$$
一眼看出,不用算。这在考试里能省大量时间。
用法二:化成三角形(性质 6 + 性质 9)
用"某行加另一行倍数"把行列式化成上三角,然后对角线一乘就完事:
$$\begin{vmatrix} 1 & 2 & 3 \\ 0 & 4 & 5 \\ 0 & 0 & 6 \end{vmatrix} = 1\times4\times6 = 24$$
💡 这是计算机算行列式的实际方法(高斯消元)。性质 6 是它的核心,
因为只有"行加行倍数"不改变行列式的值,可以放心用。
验证(数值):
| 性质 | 检验 |
|---|---|
| 交换两行变号 | $\det$ 从 $41 \to -41$ ✓ |
| 某行 ×2 | $\det$ 从 $41 \to 82$ ✓ |
| 两行相同 | $\det = 0$ ✓ |
| 上三角 | $1\cdot4\cdot6 = 24$ ✓ |
| $\det(AB)=\det A\det B$ | 两边都是 $-574$ ✓ |
4 克拉默法则 ⭐
有了行列式,方程组的解可以直接写出来。
对 $n$ 个方程、$n$ 个未知数的方程组(系数行列式 $D \ne 0$):
$$x_1 = \frac{D_1}{D}, \quad x_2 = \frac{D_2}{D}, \quad \dots, \quad x_n = \frac{D_n}{D}$$
其中 $D_i$ 是"把 $D$ 的第 $i$ 列换成常数列"得到的行列式。
4.1 完整例子(二阶)
解 $\begin{cases} 2x + y = 5 \\ x - 3y = -1 \end{cases}$
第一步,算系数行列式:
$$D = \begin{vmatrix} 2 & 1 \\ 1 & -3 \end{vmatrix} = -6-1 = -7$$
第二步,用常数列替换:
$$D_1 = \begin{vmatrix} \mathbf{5} & 1 \\ \mathbf{-1} & -3 \end{vmatrix} = -15-(-1) = -14 \qquad (\text{第 1 列换成 } 5,-1)$$
$$D_2 = \begin{vmatrix} 2 & \mathbf{5} \\ 1 & \mathbf{-1} \end{vmatrix} = -2-5 = -7 \qquad (\text{第 2 列换成 } 5,-1)$$
第三步,相除:
$$x = \frac{D_1}{D} = \frac{-14}{-7} = 2, \qquad y = \frac{D_2}{D} = \frac{-7}{-7} = 1$$
验根:$2(2)+1=5$ ✓,$2-3(1)=-1$ ✓
4.2 ⚠️ 克拉默法则的局限(很重要)
① 必须 $D \ne 0$
$D=0$ 时不能用。此时方程组要么无解,要么有无穷多解,
$$\begin{cases} x + 2y = 3 \\ 2x + 4y = 6 \end{cases} \implies D = \begin{vmatrix} 1 & 2 \\ 2 & 4 \end{vmatrix} = 0$$
第二个方程是第一个的 2 倍,只有一个独立方程,$y$ 可以随便取 → 无穷多解。
② 方程个数必须等于未知数个数
3 个未知数、2 个方程?克拉默法则管不了。
③ 数值上不实用(这条最重要)
为什么实际计算不用它:
| 维度 | 计算量 |
|---|---|
| 算 $n+1$ 个 $n$ 阶行列式 | $O(n \cdot n!)$ 级别 |
| 高斯消元法 | $O(n^3)$ |
$n=20$ 时,$20! \approx 2.4\times10^{18}$,宇宙年龄都算不完。
而 $20^3 = 8000$,一瞬间。
💡 所以克拉默法则的价值在"理论",不在"计算":
- 它给出了解的显式公式,能用来证明定理(比如解对参数的连续依赖)
- 但真要算数值,还得用消元法(numpy.linalg.solve走的就是这条路)这是数值计算的第一课:数学上"有公式"不等于"该用公式"。
阶段 3 的数值微分(h 不能太小)是同一个道理的另一面。
5 🧠 这跟认知科学有什么关系
这一节的对应关系是我验证过的(用代码实算),但心理学结论请对照文献。
行列式 = 0 ↔ 多重共线性(multicollinearity),回归分析里的大麻烦。
线性回归要求解 $X\beta = y$,正规方程是:
$$X^TX\beta = X^Ty$$
$X^TX$ 的行列式如果 = 0,这个方程组就无唯一解,回归系数算不出来。
这什么时候发生:当自变量之间线性相关时。最常见的场景是多重共线性:
两个自变量几乎携带同样的信息。
5.1 实算给你看
我构造了 30 个数据点,真实模型是 $y = 2 + 3x_1$,然后故意加一个几乎等于 $x_1$ 的 $x_2$:
| $x_2$ 与 $x_1$ 的差距 | $\det(X^TX)$ | 条件数 | 解出的系数 |
|---|---|---|---|
| 差 5 左右(独立) | $3.6\times10^{6}$ | 20 | $(1.98,\ 3.01,\ -0.005)$ ✓ |
| 差 0.5 | $4.6\times10^{4}$ | 27 | $(1.98,\ 2.98,\ 0.02)$ ✓ |
| 差 0.01 | $1.4\times10^{1}$ | 1556 | $(1.98,\ 1.46,\ 1.54)$ ⚠️ |
| 差 0.0001 | $\mathbf{1.8\times10^{-3}}$ | 134334 | $(\mathbf{1.99,\ -20.96,\ 23.96})$ ❌ |
看最后一行:真实系数是 $(2, 3, 0)$,算出来是 $(2, -21, 24)$,
完全崩了($-20.96 + 23.96 \approx 3$,两个系数在互相抵消)。
直觉:$x_1$ 和 $x_2$ 几乎一样,所以"$x_1$ 贡献多少、$x_2$ 贡献多少"无法区分,
数据给不出这个信息。任何把 3 分成 $\alpha + \beta = 3$ 的解法,拟合优度都一样。
行列式、条件数在这里的角色,是"这个模型能不能解"的度量。
$\det \to 0$ / 条件数 $\to \infty$ 就是警告信号。
5.2 这对你做研究意味着什么
| 现象 | 后果 | 检查方法 |
|---|---|---|
| 自变量高度相关 | 系数符号翻转、数值巨大 | 看条件数 / 相关矩阵 |
| 加一个被试再跑 | 系数剧烈变化 | 换样本看系数稳不稳 |
| 系数大但与理论不符 | 不是"发现",是共线性假象 | 别急着解释! |
💡 务实建议:跑完回归,一定要看一眼自变量之间的相关系数矩阵。
有两个变量相关超过 0.9?那这俩系数的解释基本不能信。
这是认知科学/心理学论文里最常见的统计问题之一。⚠️ 诚实说明:
- 上面的数值结果是我实算的(可复现),"行列式为 0 ⇒ 无唯一解"是数学定理。
- 但"共线性在心理学数据里多常见""该怎么处理"是领域问题,
请以统计教材和方法论文献为准。我保证数学,不保证研究规范。
6 数学作业 S8
- [ ] 1. 计算 $\begin{vmatrix} 3 & -2 \\ 4 & 5 \end{vmatrix}$
- [ ] 2. 计算 $\begin{vmatrix} 2 & 0 & 1 \\ 1 & 3 & 2 \\ 4 & 1 & 0 \end{vmatrix}$(用萨吕斯法则)
- [ ] 3. 不算,直接判断下列行列式是否为 0,并说明理由:
(a) $\begin{vmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 2 & 4 & 6 \end{vmatrix}$
(b) $\begin{vmatrix} 5 & 1 & 2 \\ 5 & 1 & 2 \\ 3 & 0 & 1 \end{vmatrix}$ - [ ] 4. 把 $\begin{vmatrix} 2 & 1 & 3 \\ 1 & 2 & 1 \\ 3 & 3 & 4 \end{vmatrix}$ 用"行加行倍数"化成上三角,再求值
- [ ] 5. 用克拉默法则解 $\begin{cases} 3x - y = 7 \\ x + 2y = 0 \end{cases}$
- [ ] 6. 用克拉默法则解 $\begin{cases} x + y + z = 6 \\ 2x - y + z = 3 \\ x + 2y - z = 2 \end{cases}$
- [ ] 7. 判断 $\begin{cases} x + 2y = 1 \\ 3x + 6y = 5 \end{cases}$ 是否有唯一解,说明理由
- [ ] 8.(Python)用 numpy 验证作业 6 的结果,并比较"克拉默"和"直接求解"两种做法
第 8 题参考代码:
import numpy as np
A = np.array([[1., 1., 1.],
[2., -1., 1.],
[1., 2., -1.]])
b = np.array([6., 3., 2.])
D = np.linalg.det(A)
print(f"D = {D:.6f}")
# 克拉默法则:逐列替换
for i, name in enumerate("xyz"):
Ai = A.copy() # ← 注意:要 copy!否则会改到 A
Ai[:, i] = b
print(f"{name} = {np.linalg.det(Ai) / D:.10f}")
# 更常用的直接解法
print("numpy.linalg.solve:", np.linalg.solve(A, b))
⚠️ 注意
Ai = A.copy(),不拷贝会直接改掉A,
第二次循环就全错了。这正是阶段 7 学的可变性陷阱,
在这里以最隐蔽的方式出现。
第二部分 · 编程
7 字典:键值对的集合
列表按"位置"取东西(lst[0]);字典按"名字"取东西(d["姓名"])。
person = {"姓名": "张三", "年龄": 18, "专业": "认知科学"}
print(person["姓名"]) # 张三
为什么需要字典:你要取的是"名叫'年龄'的那个",不是"第 3 个"。
7.1 基本操作
| 操作 | 例子 | 说明 |
|---|---|---|
| 取值 | d["年龄"] |
键不存在 → KeyError |
| 安全取值 | d.get("年龄") |
不存在返回 None(不报错) |
| 带默认值 | d.get("身高", 0) |
不存在返回 0 |
| 赋值/新增 | d["身高"] = 175 |
有则改,无则加 |
| 判断存在 | "年龄" in d |
True / False |
| 删除 | d.pop("年龄") |
返回被删的值 |
| 所有键 | d.keys() |
|
| 所有值 | d.values() |
|
| 所有键值对 | d.items() |
配合 for k, v in ... |
for k, v in person.items():
print(f"{k} = {v}")
⚠️
d["不存在的键"]vsd.get("不存在的键"),这是最常用的一组区别:
-d[...]会报KeyError
-d.get(...)返回None不确定键在不在,就用
.get()。否则程序会因为一个缺失的键而崩掉。
7.2 键必须是"可哈希"的
| 能当键 | 不能当键 |
|---|---|
数字、字符串、元组、None |
列表、字典、集合 |
{(1, 2): "可以"} # ✓ 元组可哈希
{[1, 2]: "不行"} # ✗ TypeError: unhashable type: 'list'
💡 为什么:字典靠"哈希"快速查找。可变对象哈希值会变,
一旦变了,字典就找不到它了。所以只允许不可变的东西当键。这也解释了为什么阶段 7 说"可变对象麻烦",限制无处不在。
7.3 字典保持插入顺序(Python 3.7+)
d = {"b": 1, "a": 2}
print(list(d.keys())) # ['b', 'a'] ← 按插入顺序,不是字母序
8 集合:只关心"有没有"
集合是无序、不重复的元素集合。
s = {1, 2, 3}
print(s) # {1, 2, 3}
⚠️ 空集合必须写
set()!
{}是空字典,不是空集合。
print(type({})) # <class 'dict'> ← 注意!
print(type(set())) # <class 'set'>
8.1 最主要的用途:去重
nums = [1, 1, 2, 2, 3, 3]
print(set(nums)) # {1, 2, 3}
print(list(set(nums))) # [1, 2, 3]
一行去重。比写循环简单得多。
⚠️ 代价:顺序丢了。集合无序,所以"去重后保持原顺序"要另想办法
(阶段 7 的remove_duplicates就是这个)。
8.2 集合运算(数学里的那套)
| 运算 | 符号 | 例子 | 结果 |
|---|---|---|---|
| 并集 | \| |
{1,2,3,4} \| {3,4,5,6} |
{1,2,3,4,5,6} |
| 交集 | & |
{1,2,3,4} & {3,4,5,6} |
{3,4} |
| 差集 | - |
{1,2,3,4} - {3,4,5,6} |
{1,2} |
| 对称差 | ^ |
{1,2,3,4} ^ {3,4,5,6} |
{1,2,5,6} |
交集和差集最常用:
被试A = {"张三", "李四", "王五"}
被试B = {"李四", "王五", "赵六"}
print(被试A & 被试B) # {'李四', '王五'} 都参加过的
print(被试A - 被试B) # {'张三'} 只 A 有的
💡 查找速度:
x in 集合是接近瞬时的(哈希查找),
而x in 列表要逐个扫。数据量大时差距巨大。
不需要顺序、只需要"在不在"时,用集合。
9 🔧 技能卡 8:数据结构怎么选
你现在会三种容器了。遇到问题该用哪个?这张卡给你一个决策清单。
9.1 三选一:一张表
| 想要什么 | 用什么 | 看家本领 |
|---|---|---|
| 按顺序排列、可改 | 列表 [] |
索引、切片、append |
| 按名字取、一一对应 | 字典 {} |
d["键"],查找快 |
| 只关心有没有、去重 | 集合 set() |
去重、in 快、集合运算 |
9.2 决策三问
① 我需要"顺序"吗?
- 要 → 列表
- 不要,只问"在不在" → 集合
② 我需要"名字 → 值"的对应吗?
- 要(比如"每个被试的反应时")→ 字典
- 不要 → 列表或集合
③ 我需要"快"吗?
| 操作 | 列表 | 字典/集合 |
|---|---|---|
按位置取 x[5] |
快 | — |
按名字取 d["k"] |
— | 快 |
问"在不在" x in c |
慢(逐个扫) | 快(哈希) |
| 去重 | 慢 | 快 |
9.3 一个真实的选型例子
任务:统计一段文本里每个词出现几次。
思考过程:
- 需要"词 → 次数"的对应 → 字典
- 那"有哪些词"呢?→ 字典的键天然就是
def word_count(text):
counts = {}
for w in text.split():
counts[w] = counts.get(w, 0) + 1 # 不存在当 0,再 +1
return counts
print(word_count("a b a c a b"))
# {'a': 3, 'b': 2, 'c': 1}
💡
counts.get(w, 0) + 1这个写法非常常用,
"如果没出现过就当 0,然后加 1"。省掉了if w in counts:的分支判断。
再问:如果要"出现过的词有哪些(不重复)"?
→ 集合:set(text.split())。或者 set(counts.keys())。
9.4 别过度优化
⚠️ 可读性优先:数据量小(几千个)时,随便用哪个都行。
先写清楚,真慢了再换。过早优化是万恶之源。💡 什么时候真的会疼:数据到了十万以上,且在循环里反复查找。
那时候把列表换成集合/字典,速度可能差几百倍。
这是你以后处理实验数据时会真的遇到的问题。
10 🧠 这跟认知科学有什么关系(编程侧)
概念性内容,无法用代码验证,请对照文献。
字典 ↔ 认知建模里的「联想记忆」和「查找表」。
认知科学里有一个经典模型叫联想记忆(associative memory):
记忆是"线索 → 内容"的映射。给它一个线索(键),取回内容(值)。
这和字典是同构的:
| 字典 | 联想记忆 |
|---|---|
d[键] 取回值 |
给线索,回忆内容 |
| 键 → 值的映射 | 线索 → 记忆的联结 |
KeyError(键不存在) |
遗忘 / 提取失败 |
d.get(键, 默认值) |
提取失败时用默认/猜测填补 |
| 键必须唯一 | 一个线索对应一个记忆 |
"提取失败"和"没存储"是两回事,这是记忆研究的核心区分之一。
在字典里,它就是 KeyError(根本没有这个键)和值恰好是 None 的区别。
这个概念在编程和认知科学里都需要你分清楚。
集合 ↔ 「特征集合」式的概念表征。
有一类理论认为概念就是一组特征的集合(feature-set / 概念的特征模型):
| 集合运算 | 概念表征 |
|---|---|
| 交集 $A \cap B$ | 两个概念的共同特征("鸟"和"鱼"共有的特征) |
| 并集 $A \cup B$ | 上位概念的特征("动物") |
| 差集 $A - B$ | 区别性特征("鸟"有而"鱼"没有的) |
典型性效应:为什么麻雀比企鹅更像"鸟"?
特征集合模型给出的解释是,麻雀与"鸟"的原型特征集合的交集更大。
⚠️ 重要提醒:
- "特征集合模型"是一个历史上的重要模型,但它有已知的失败案例
(典型性效应、组合性、模糊边界都难解释),后来被原型理论、样例理论等取代或补充。
- 这一节是让你看到"数学结构在认知建模里真的被用到",不是让你把它当结论。
请以文献为准。
11 编程作业 S8
- [ ] 1. 用字典存 3 个同学的"姓名 → 成绩",打印每个人的成绩和平均分
- [ ] 2. 写
word_count(text),返回每个词出现次数的字典(参考上面的写法) - [ ] 3. 写
invert(d),把字典的键值对反过来(值当键、键当值)注意:如果值有重复会怎样?先想清楚,再决定怎么处理
- [ ] 4. 给定两个列表,用集合求:共同元素、只在第一个里的、所有不重复元素
- [ ] 5. 用集合去重,再和阶段 7 写的
remove_duplicates比较:两者结果一样吗?什么时候不一样?(提示:想想"顺序")
- [ ] 6.(重点)下面代码有 bug。用阶段 8 的
Ai = A.copy()那个坑来解释它:d1 = {"a": 1} d2 = d1 d2["b"] = 2 print(d1) # 期望 {"a": 1},实际?- [ ] 7. 写
merge_counts(c1, c2),把两个"词频字典"合并(同词相加)
- [ ] 7. 写
第 3 题参考写法:
def invert(d):
result = {}
for k, v in d.items():
result[v] = k # ⚠️ 值重复时,后面的会覆盖前面的
return result
print(invert({"a": 1, "b": 2})) # {1: 'a', 2: 'b'}
print(invert({"a": 1, "b": 1})) # {1: 'b'} ← 1 被覆盖了!
⚠️ 这就是"值必须唯一"才能安全反转。
想保留所有,就得把值存成列表:{1: ['a', 'b']}。
写代码前先想清楚"重复会发生什么",这就是边界思维。
第 7 题参考写法:
def merge_counts(c1, c2):
result = c1.copy() # ← 先拷贝,别改传入的字典(阶段 7 的教训)
for k, v in c2.items():
result[k] = result.get(k, 0) + v
return result
a = {"x": 1, "y": 2}
b = {"y": 3, "z": 1}
print(merge_counts(a, b)) # {'x': 1, 'y': 5, 'z': 1}
print(a) # {'x': 1, 'y': 2} ← 原字典没被改 ✓
assert merge_counts({"a": 1}, {}) == {"a": 1}
assert merge_counts({}, {"a": 1}) == {"a": 1}
print("检查用例通过")
第 6 题答案:
d1 = {"a": 1}
d2 = d1 # ⚠️ 别名!不是拷贝
d2["b"] = 2
print(d1) # {'a': 1, 'b': 2} ← d1 也被改了
和
Ai = A.copy()是同一个病:d2 = d1只是又贴了一个标签,
两个名字指向同一个字典。改一个,另一个也变。
正确写法:d2 = d1.copy()。
本阶段自评
- 数学:二阶行列式怎么算?三阶的萨吕斯法则有几项?
- 数学:萨吕斯法则能用于四阶吗?为什么?
- 数学:哪条性质让你"不算就知道行列式是 0"?
- 数学:计算机算行列式实际用哪条性质?为什么是它?
- 数学:克拉默法则的三个局限分别是什么?
- 数学:为什么"有公式"不等于"该用公式"?举本阶段的例子。
- 编程:
d["键"]和d.get("键")有什么区别?什么时候用哪个? - 编程:为什么列表不能当字典的键?这和阶段 7 的"可变"有什么关系?
- 编程:空集合怎么表示?为什么
{}不行? - 技能:三选一决策清单,列表/字典/集合各自适合什么场景?
- 连接:为什么自变量高度相关会让回归系数"崩掉"?$\det(X^TX)$ 在这里是什么角色?
全部答得上 → 大一上阶段 1–8 完成,进阶段 9。