资讯动态

逆序数、高斯消元与克莱姆法则:理解行列式的工程视角

发布时间:2026/9/19 13:03:59 来源:尧图企业网站定制
简介这是一份围绕线性代数核心概念“行列式”整理的练习题及答案文档适合高等院校理工科学生、考研复习者以及自学者用作课后巩固与考前训练。内容全面覆盖逆序数、行列式项符号、奇排列、n阶行列式的三种定义写法、零元素个数与行列式值的关系以及克莱姆法则、齐次线性方程组有非零解的条件等考点题型包含填空题、选择题、计算题和证明题文末附有对照答案便于逐题自评与查漏补缺帮助学习者系统掌握行列式的计算技巧及其在线性方程组中的应用。资源为1个doc文档压缩包大小369KB文件内容为第1章行列式的多套作业与检测题结构清晰可按章节顺序练习。已有242人学习下载适合需要专项突破线性代数行列式模块的读者使用。1. 从逆序数到行列式符号一份练习资源里的核心概念如果只把行列式当成numpy.linalg.det的一个封装那你可能永远也说不清为什么矩阵奇异会导致方程无解、为什么会计算出来的行列式是-0.0。最近我拆了一份线代练习资料《第 1 章行列式》作业与检测题它的内容不复杂但正好把逆序数、项符号、代数余子式、克莱姆法则这几个点按“定义 → 计算 → 应用”的顺序串了一遍。对于做算法、做数值计算、甚至做机器学习特征工程的工程师这一套补下来再看矩阵求逆、最小二乘和稀疏方程求解会有一种“原来括号里的代码在干什么”的通透感。这份资源的作业 1 到作业 4 加检测题表面上只是填空题、选择题和计算题但每道题都能映射到一段可以写出来的逻辑排列的逆序数怎么数、n 阶行列式的每一项符号怎么定、复杂行列式怎么拆项、齐次方程组什么时候有非零解。下面我按自己拆题的习惯把这几块拆成可复现的代码和排错经验。2. 逆序数与项符号为什么定义式要关心排列的奇偶性行列式的定义式不是“对角线乘积相减”的推广。它本质上是把所有不同行、不同列的元素乘起来再配上由行指标和列指标排列奇偶性决定的符号。这个符号来自逆序数。作业 1 里第一道填空题就在问“排列 1 3 … 2 4 … 的逆序数”第二道问“在 6 阶行列式中这一项的符号”都是同一个底层逻辑。2.1 逆序数的实现从“1 3 … 2 4 …”说起逆序数的定义很直白在一个排列中如果较大的数出现在较小的数前面就算一个逆序对。比如排列1 3 2 4里只有3 2这一对逆序数是 1。写一个双重循环就能数def inversion_count(perm): count 0 n len(perm) for i in range(n): for j in range(i 1, n): if perm[i] perm[j]: count 1 return count print(inversion_count([1, 3, 2, 4])) # 1 print(inversion_count([1, 3, 5, 2, 4])) # 3这段代码的参数是排列列表perm。双循环固定左边的位置i然后扫描右边所有位置j只要发现左大右小就累加。复杂度是 O(n²)对练习题目里的 n 完全够用。如果要在大规模排列里用可以用树状数组优化到 O(n log n)但没必要。作业 1 里“排列 1 3 … 2 4 …”写法省略了中间项。这种题通常考察的是当你把奇数按升序排、偶数按升序排后再拼接逆序数会落在哪个范围。直接用上面的函数把完整排列填进去就不需要背诵任何公式。2.2 奇排列个数一个容易记反的结论作业 1 还有一空“所有 n 元排列中奇排列的个数共__个。”答案是n! / 2而不是摘要里容易误写的2^(n-1)。原因是交换任意两个相邻元素会改变排列的奇偶性所以奇排列和偶排列之间存在一一对应关系总数必须对半。n 元排列所有排列数奇排列数偶排列数n2211n3633n4241212n51206060用 Python 穷举验证也很直接from itertools import permutations def count_parity(n): odd 0 even 0 for p in permutations(range(n)): cnt inversion_count(list(p)) if cnt % 2 0: even 1 else: odd 1 return odd, even print(count_parity(4)) # (12, 12)注意这里inversion_count是上一节定义的函数。permutations(range(n))生成所有 n 元排列再用逆序数的奇偶性分类。这个验证在答题时不方便但在开发时特别适合写成一个自测函数。2.3 项的符号行标列标逆序数之和判断行列式中某一项的符号标准做法是看行标排列和列标排列的逆序数之和。若为偶数取正号奇数取负号。作业 1 的选择题和填空题本质都在考这一点。def term_sign(row_perm, col_perm): s inversion_count(row_perm) inversion_count(col_perm) return 1 if s % 2 0 else -1 # 例三阶行列式中 a11 a23 a32 print(term_sign([1, 2, 3], [1, 3, 2])) # -1这里row_perm是元素所在行号组成的排列col_perm是列号排列。因为行列式定义要求每行每列只取一个元素所以这两个排列都是[1..n]的重排。符号为 1 表示该项前是正号为 -1 表示负号。在工程上如果看到生产代码里有人用三重循环按定义展开求行列式大概率就是忽略了符号判断。符号错一个整个结果符号就反了。这也是作业 1 反复训练的价值。3. 从定义展开到消元法n 阶行列式计算的工程选择作业 2 开始进入实际计算四个二阶和三阶混合行列式外加一个 n 阶行列式。从小题到大题正好对应两条技术路线递归展开和行化简。两条路线在练习里都能出结果但到了代码里性能差距巨大。3.1 为什么不用拉普拉斯展开写生产代码按定义展开也叫拉普拉斯展开。n 阶行列式可以按某一行或列展开成 n 个子行列式之和。写成递归很好懂def det_laplace(a): n len(a) if n 1: return a[0][0] if n 2: return a[0][0] * a[1][1] - a[0][1] * a[1][0] total 0 for col in range(n): minor [row[:col] row[col1:] for row in a[1:]] total ((-1) ** col) * a[0][col] * det_laplace(minor) return total print(det_laplace([[1, 2], [3, 4]])) # -2 print(det_laplace([[2, 0, 1], [0, 1, 2], [1, 2, 0]])) # -3递归参数a是二维列表。每次固定第一行第col列删掉第一行和第col列得到余子式。(-1) ** col是代数余子式符号因为第一行的行标固定为 1符号只由列标col1决定。这段代码能跑通 3 阶、4 阶但到 10 阶就会卡死因为复杂度是 O(n!)。方法时间复杂度适用阶数数值稳定性定义展开O(n!)n ≤ 8符号递归会累积误差高斯消元O(n³)n 可到几千选主元后较好对角化 / 拆项取决于结构特殊结构精准所以生产代码里几乎不会用递归展开。它只适合教学以及用来生成小矩阵的精确验证。3.2 行化简求行列式稳定且可复用工程上最常用的方法是高斯消元。只用三种行变换交换两行行列式变号某行乘以常数 k行列式变成 k 倍某行加上另一行的 k 倍行列式不变。按这个规则把矩阵化成上三角行列式就是对角线元素乘积。import numpy as np def det_by_gauss(a): a np.array(a, dtypefloat) n a.shape[0] det 1.0 for col in range(n): # 列主元选择当前列绝对值最大的行 pivot max(range(col, n), keylambda r: abs(a[r, col])) if np.isclose(a[pivot, col], 0.0): return 0.0 if pivot ! col: a[[col, pivot]] a[[pivot, col]] det * -1 pivot_val a[col, col] det * pivot_val # 只消当前列下方的元素 for r in range(col 1, n): factor a[r, col] / pivot_val a[r, col:] - factor * a[col, col:] return det a np.array([[2, 1, -1], [1, 3, 2], [-1, 2, 1]], dtypefloat) print(det_by_gauss(a)) # -14.000000000000002 print(np.linalg.det(a)) # -14.000000000000002这段代码的关键是pivot的选择。每一列都从当前及以下行里挑绝对值最大的元素当主元避免除以一个接近零的数。a[r, col] - factor * a[col, col:]中a[col, col:]是当前主元行从第col列开始的部分这样能保持前面已经消好的列不受影响。det * pivot_val是因为行化简后对角线乘积要乘回来而不是直接读乘积。实际工作中我更推荐直接用np.linalg.det但确认它内部用的也是类似的主元消元。自己写一遍的意义在于理解为什么det0时np.linalg.solve会炸因为消元过程里出现了零主元。3.3 拆项法作业 2 里“n2 时用拆项法”的工程含义作业 2 第 5 题计算 n 阶行列式答案写的是“当 n2 时直接算当 n2 时用拆项法可得”。拆项法说的是如果某一行能拆成两个行向量之和那么这个行列式可以拆成两个行列式之和。很多带相同结构的 n 阶行列式拆一次后会出现大量相同的行从而消成三角阵。常见做法是遇到形如逐行都带通项的数字矩阵先别急着展开而是把第一列中除第一个元素外的部分拆成“常数项 变量项”。拆开后一行是常数一行是变量再观察能否通过行相减消出上三角。这种技巧在手工题里很省力但在代码里我一般不会真的去拆项而是直接套消元法。拆项法的价值在于给程序处理特殊矩阵时提供降维思路。比如三对角矩阵、范德蒙德矩阵显式构造并消元会浪费内存拆出递推式可以把它变成一维循环。4. 克莱姆法则、代数余子式与齐次方程组从练习到数值求解作业 3 和作业 4 把行列式从“算数值”推向“解方程组”。作业 3 第一道选择题在问代数余子式展开时某个式子是否恒等。作业 4 第一道填空题直接写“克莱姆法则下方程组有唯一解的条件是系数行列式不为零”。4.1 代数余子式展开作业 3 背后是递归定义代数余子式A_ij与余子式M_ij相差一个符号(-1)^(ij)。作业 3 的计算题要求用代数余子式求具体行列式本质是在练习展开路径的选择。def algebraic_cofactor(a, i, j): # i, j 从 0 开始 minor [row[:j] row[j1:] for row in (a[:i] a[i1:])] sign -1 if (i j) % 2 1 else 1 return sign * det_laplace(minor)a是二维列表i、j是要删除的行列索引。row[:j] row[j1:]删掉第j列a[:i] a[i1:]删掉第i行。sign对应代数余子式的符号。工程里代数余子式最常见的应用是伴随矩阵求逆inv(A) adj(A) / det(A)。但当你真的用伴随矩阵去求 20 阶矩阵的逆时会算出 20 个 n-1 阶子行列式复杂度接近 O(n^5)。所以它只适合小矩阵或推导公式。生产环境要逆矩阵还是用 LU 分解。4.2 克莱姆法则的适用边界用行列式解线性方程组的方法是克莱姆法则。对A x b如果det(A) ! 0那么每个未知数x_j det(A_j) / det(A)其中A_j是把A的第j列替换成b后得到的矩阵。直接实现import numpy as np def cramer_solve(A, b): A np.array(A, dtypefloat) b np.array(b, dtypefloat) D np.linalg.det(A) if np.isclose(D, 0): return None n len(b) xs [] for j in range(n): Aj A.copy() Aj[:, j] b xs.append(np.linalg.det(Aj) / D) return np.array(xs) A np.array([[2, 1, -1], [1, 3, 2], [-1, 2, 1]], dtypefloat) b np.array([8, 13, 7], dtypefloat) print(cramer_solve(A, b)) print(np.linalg.solve(A, b))D是系数行列式。Aj[:, j] b直接替换第j列。np.isclose用来判断 D 是否为零。这个解法的理论价值大于实用价值因为每算一个未知数都要重新求一次det总共要做 n1 次 O(n³) 的消元而直接linalg.solve只需要一次 O(n³)。所以克莱姆法则适合低阶、符号推导或理论证明。4.3 齐次线性方程组有非零解的行列式判据作业 4 第三题问“当取何值时齐次线性方程组有非零解”。齐次方程A x 0除了零解之外还有其他解的唯一判据是det(A) 0。如果用符号计算可以让程序自己找参数import sympy as sp lam sp.symbols(lam) A sp.Matrix([[1 - lam, 2, 0], [0, 2 - lam, 1], [1, 0, 3 - lam]]) det_expr A.det() print(sp.factor(det_expr)) print(sp.solve(sp.Eq(det_expr, 0), lam))这段代码用sympy.Matrix构造带参数矩阵A.det()会做符号展开而不是数值计算。sp.solve把行列式等于零的方程解出来。这样无论参数藏在哪个位置都比手工配方快。工程上这类问题出现在判断系统是否可控、特征值是否重复、以及稳定性分析里。系数行列式 det(A)非齐次方程组 A x b齐次方程组 A x 0不为零唯一解只有零解为零无解或无穷多解存在非零解这张表是克莱姆法则和齐次方程组判据的统一。练习资源里作业 4 填空第 2 问的“必要条件”和“充要条件”的区别在代码里对应np.isclose(det, 0)这个判断执行前后的分支逻辑。5. 用符号计算批量校验练习答案一个排错技巧拿到这份 doc 资源除了手动做题我更喜欢把它改造成一份可自动校验的题库。做法很简单把每道行列式题表示成符号矩阵再用sympy算出精确值和资源答案逐项对比。这样既能确认资料答案有没有打印错误也能在改题时快速回归。5.1 构造一个简单的批量校验脚本对作业 2 和检测题里的具体矩阵我会维护一个列表每个元素是“题目名矩阵期望答案”。然后用sympy统一计算import sympy as sp import numpy as np cases [ (3阶, [[2, 0, 1], [0, 1, 2], [1, 2, 0]], -3), (4阶, [[1, 0, 2, -1], [0, 1, 0, 2], [2, 0, 1, 1], [1, 2, 0, 1]], None), ] for name, mat, expected in cases: value sp.Matrix(mat).det() if expected is not None: status OK if value expected else MISMATCH print(f{name}: {value} vs {expected} - {status}) else: print(f{name}: {value})sympy的结果是精确数不会出现1.9999999999999996这种误差。如果资料里的答案和你用numpy算的结果差一点点通常不是题错了而是浮点误差。5.2 一个易踩的坑零行列式不等于0.0np.linalg.det对奇异矩阵返回的往往不是严格的 0而是一个接近 0 的小数比如-4.4e-16。如果直接写if det 0就会把一个真正奇异矩阵判成非奇异导致后面解方程时得到巨大数值或直接报LinAlgError。M np.array([[1, 2], [2, 4]], dtypefloat) print(np.linalg.det(M)) # -4.440892098500626e-16 print(np.isclose(np.linalg.det(M), 0)) # True正确做法是用np.isclose加一个阈值比如atol1e-10。或者在需要精确判断时用sympy.Matrix(M).det()得到精确的 0。这个坑在推导齐次方程组是否有非零解时尤其致命你算出来的det 0.0000000001就会错误地认为只有零解。最后把这个脚本挂在你的标准化练习目录里每次做完题跑一遍哪个答案对不上会直接标出来。比人肉检查快也能顺便验证自己改过的行列式是否保持了原题结构。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价