2026/10/13 22:58:08

线性规划与单纯形法:从建模到Python求解的完整指南

线性规划与单纯形法:从建模到Python求解的完整指南 1. 从“拍脑袋”到“算最优”线性规划到底在解决什么问题很多人第一次接触运筹学看到“线性规划”四个字脑子里蹦出来的第一反应是“这玩意儿跟编程有什么关系”。其实这里的“规划”不是写代码而是“谋划”和“计划”——它要解决的核心问题非常朴素在资源有限的情况下怎么分配才能让目标最大化或最小化。举个最接地气的例子。假设你是一个小作坊的负责人手头有两条生产线一条做产品A一条做产品B。A每件利润300元B每件利润200元。但生产A需要消耗2小时人工和1公斤原料生产B需要1小时人工和3公斤原料。你每天只有120小时人工和150公斤原料。那么问题来了A和B各生产多少件才能让总利润最高这就是一个标准的线性规划问题。它的三个要素非常清晰决策变量A和B各做多少、目标函数总利润最大化、约束条件人工和原料不能超。所谓“线性”指的是目标函数和约束条件都是决策变量的线性表达式——没有平方、没有开根号、没有指数干干净净的一次关系。为什么线性这么重要因为一旦模型是线性的它的可行域就是一个凸多边形或高维凸多面体而凸性保证了一个关键性质如果存在最优解那它一定可以在可行域的某个顶点上取到。这个性质是整个单纯形法的理论基石。你可以把它想象成在一个多边形围栏里找最高点如果围栏的地面是平的、围栏壁是直的那最高点一定在某个角落上而不是在围栏中间某个莫名其妙的位置。线性规划的历史可以追溯到上世纪三四十年代。当时二战期间军方需要解决物资调配、运输路线、人员安排等一系列资源分配问题传统的经验判断已经不够用了。到了1947年一位叫丹齐格的学者提出了单纯形法这才让线性规划从理论走向了真正可计算、可落地的工具。此后几十年线性规划在工业、交通、金融、农业等领域遍地开花成为运筹学中最基础也最核心的分支。你可能会问现在计算机这么强直接暴力搜索不行吗不行。决策变量稍微多一点比如几十个变量、上百个约束搜索空间就是天文数字。单纯形法的精妙之处在于它不需要遍历所有顶点而是沿着可行域的边“聪明地”从一个顶点跳到另一个顶点每次跳跃都让目标函数值变得更好直到再也找不到更好的方向为止。这种“贪心但不盲目”的策略让它在实践中极其高效。这一章要讲的内容就是围绕这个核心思路展开先把实际问题翻译成线性规划的标准形式然后理解可行域和顶点的几何意义接着掌握单纯形表的操作流程最后学会处理各种“不标准”的情况——比如约束是大于等于、变量可以取负值、目标函数要求最小化等等。这些看起来是技术细节但恰恰是实际建模时最常卡住的地方。2. 把现实问题翻译成数学语言建模的四个关键动作2.1 决策变量怎么设决定了模型的成败建模的第一步也是最容易出错的一步就是设定决策变量。决策变量是你真正能控制、需要求解的量。设得好模型清晰简洁设得不好后面越算越乱。回到前面小作坊的例子决策变量就是“每天生产A的件数”和“每天生产B的件数”可以记作x₁和x₂。这里有个经验决策变量的单位要明确含义要具体。不要设一个模糊的“资源分配量”而要设“从仓库i运往门店j的货物箱数”这种一看就懂的量。还有一个常见坑有些量看起来是变量其实是约束中派生出来的。比如“总利润”不是决策变量它是目标函数的值“剩余人工时间”也不是决策变量它是约束的松弛量。把派生量误设为决策变量会导致模型冗余甚至矛盾。2.2 目标函数的系数从哪来目标函数是你最终要优化的那个指标。最大化利润、最小化成本、最短时间、最少用料都是常见目标。目标函数里每个决策变量前面的系数代表这个变量对目标的“贡献率”。在利润最大化的例子里A的系数是300B的系数是200。这两个数字必须来自实际业务数据不能拍脑袋。如果系数估错了算出来的“最优解”在现实中可能根本不优。我见过一个案例某团队在优化配送路线时把“每公里油耗成本”估成了固定值但实际上不同路况油耗差异很大导致模型推荐了一条高速优先的路线实际跑下来成本反而更高。所以目标系数尽量用实测数据或可靠的历史统计值实在没有也要做敏感性分析看看系数在多大范围内波动时最优解不变。2.3 约束条件的三种类型与转化技巧约束条件分三类小于等于≤、大于等于≥、等于。标准形式的线性规划通常要求所有约束都是等式右边常数非负。但实际建模时≤和≥才是常态。怎么把不等式变成等式引入松弛变量和剩余变量。对于“2x₁ x₂ ≤ 120”这个人工约束引入松弛变量s₁ ≥ 0变成“2x₁ x₂ s₁ 120”。s₁的物理意义是“没用完的人工时间”。对于“x₁ x₂ ≥ 10”这种最低产量约束引入剩余变量s₂ ≥ 0变成“x₁ x₂ - s₂ 10”s₂表示“超出最低产量的部分”。这里有个细节松弛变量和剩余变量在目标函数中的系数都是0因为它们不影响利润或成本只是把不等式“填平”成等式。但它们在单纯形表中扮演着重要角色——初始基变量往往就是这些松弛变量。2.4 变量取值范围的隐藏陷阱标准形式要求所有变量非负。但现实中有些变量确实可以取负值比如“某仓库的净流入量”可能是负的表示流出。这时候不能直接套标准形式需要做变量替换令x x - x其中x ≥ 0x ≥ 0。这样x就可以取任意实数了。还有一种情况是变量有上下界比如“某产品产量不超过50件”。这可以作为一个约束写进去也可以在单纯形法中用“有界变量”技巧处理减少约束数量。对于手算练习直接写成约束更直观对于大规模计算用有界变量技巧更高效。把上面四步走完一个实际问题就变成了标准形式的线性规划模型。接下来就是求解。而求解的核心工具就是单纯形法。3. 单纯形法的几何直觉为什么沿着边走就能找到最优解3.1 可行域是一个凸多面体把所有约束条件画在坐标系里满足所有约束的点集就是可行域。对于两个变量的情况可行域是一个凸多边形三个变量是一个凸多面体更高维就是凸多胞体。所谓“凸”意思是可行域内任意两点的连线仍然完全在可行域内。这个凸性非常关键。它保证了可行域没有“凹进去的缺口”也没有“孤岛”。你可以把可行域想象成一个凸出来的山坡山顶最优点一定在边界上而且一定在某个“角”上。这个“角”在数学上叫顶点或基本可行解。为什么最优点一定在顶点直观理解假设最优点在一条边的中间那沿着这条边往两个方向走目标函数值一个变大一个变小所以中间那个点不可能是最大点。同理最优点也不可能在面的内部。所以最优点只能在顶点上。这个结论对任何维度的线性规划都成立。3.2 顶点与基变量的对应关系在代数上顶点对应的是基本可行解。一个有n个变量、m个约束的标准线性规划如果m n那么可以选m个变量作为基变量剩下的n-m个变量作为非基变量并令它们为0然后解出基变量的值。如果解出来的基变量都非负这个解就是基本可行解对应一个顶点。举个例子2个变量、2个约束加上松弛变量后变成4个变量、2个约束。选2个基变量令另外2个为0解出基变量。如果基变量都≥0就是一个顶点。总共有C(4,2)6种选法对应最多6个顶点。单纯形法就是在这6个顶点中沿着边“跳”到目标函数值更好的那个直到找不到更好的为止。3.3 检验数判断当前顶点是否最优的标尺单纯形法每一步都要回答一个问题当前顶点是不是最优的如果不是往哪个方向走这个判断靠的是检验数。检验数的计算方式是对于每个非基变量计算它在目标函数中的系数减去它“替换”基变量时引起的目标函数变化。如果所有非基变量的检验数都≤0最大化问题说明任何非基变量增加都不会让目标函数变大当前顶点就是最优解。如果某个非基变量的检验数0说明让它进基可以改善目标函数就选它进基。选哪个非基变量进基通常选检验数最大的那个这叫最大改进策略。选哪个基变量出基用最小比值法则用当前基变量的值除以该非基变量在对应约束中的系数取最小的正比值对应的基变量出基。这个法则保证新解仍然是可行的所有变量非负。3.4 单纯形表的迭代过程就像“换座位”单纯形表是把所有系数、基变量、检验数放在一张表格里通过行变换实现“换基”操作。每次迭代选一个非基变量进基选一个基变量出基然后做高斯消元让进基变量对应的列变成单位向量。这个过程就像教室里换座位一个学生变量从后排非基换到前排基另一个学生从前排换到后排然后调整所有人的位置关系。迭代终止的条件有两个一是所有检验数≤0找到最优解二是某个非基变量的检验数0但它对应的所有约束系数都≤0说明这个变量可以无限增大而不违反任何约束目标函数值可以无限增大这叫无界解。实际建模中遇到无界解通常意味着漏掉了某个约束需要回去检查模型。4. 手把手走一遍单纯形表从初始表到最优解4.1 构建初始单纯形表还是用前面的例子最大化 Z 300x₁ 200x₂约束为 2x₁ x₂ ≤ 120x₁ 3x₂ ≤ 150x₁, x₂ ≥ 0。引入松弛变量s₁和s₂变成标准形式最大化 Z 300x₁ 200x₂ 0s₁ 0s₂约束 2x₁ x₂ s₁ 120 x₁ 3x₂ s₂ 150 x₁, x₂, s₁, s₂ ≥ 0初始基变量选s₁和s₂因为它们的系数列是单位向量初始基本可行解是x₁0, x₂0, s₁120, s₂150目标函数值Z0。初始单纯形表如下基变量x₁x₂s₁s₂右端项s₁2110120s₂1301150Z-300-200000注意Z行写的是检验数的相反数或者直接写检验数但符号约定要统一。这里用“-300”表示x₁的检验数是300因为Z行是“目标函数系数减去当前基变量的贡献”。4.2 第一次迭代让x₁进基检验数中最大的是x₁的300对应Z行-300所以选x₁进基。计算比值120/2 60150/1 150。最小正比值是60对应s₁出基。以2为主元做行变换第一行除以2得到新第一行x₁ 0.5x₂ 0.5s₁ 60。然后用第一行消去第二行和Z行中的x₁系数。第二行减去第一行0x₁ 2.5x₂ - 0.5s₁ s₂ 90。 Z行加上300倍第一行0x₁ - 50x₂ 150s₁ 0s₂ 18000。更新后的表基变量x₁x₂s₁s₂右端项x₁10.50.5060s₂02.5-0.5190Z0-50150018000当前解x₁60, x₂0, s₁0, s₂90Z18000。4.3 第二次迭代x₂进基检验数中x₂对应-50说明x₂的检验数是500还可以改进。选x₂进基。比值60/0.5 12090/2.5 36。最小正比值36对应s₂出基。以2.5为主元做行变换第二行除以2.5得到x₂ - 0.2s₁ 0.4s₂ 36。然后用第二行消去第一行和Z行中的x₂系数。第一行减去0.5倍第二行x₁ 0x₂ 0.6s₁ - 0.2s₂ 42。 Z行加上50倍第二行0x₁ 0x₂ 140s₁ 20s₂ 19800。最终表基变量x₁x₂s₁s₂右端项x₁100.6-0.242x₂01-0.20.436Z001402019800所有检验数≤0最优解为x₁42, x₂36最大利润Z19800元。4.4 结果解读与敏感性分析算出来x₁42、x₂36意思是每天生产42件A和36件B总利润19800元。此时人工约束和原料约束都刚好用完s₁0, s₂0说明两种资源都是瓶颈。如果人工时间从120增加到121总利润会增加多少看Z行中s₁的系数140意思是人工时间每增加1小时利润增加140元。这个数字叫影子价格它告诉你哪种资源最值得追加。原料的影子价格是20元/公斤远低于人工的140元/小时。所以如果只能追加一种资源优先追加人工。影子价格还有一个用途判断某个资源的价格是否值得购买。如果人工的市场价是100元/小时影子价格是140元/小时那追加人工是划算的如果市场价是160元/小时那就不划算了。5. 那些教科书不细说但实际建模常踩的坑5.1 退化与循环为什么算着算着卡住了退化是指某个基本可行解中有基变量的值为0。这时候用最小比值法则选出的出基变量可能不唯一如果选得不好可能出现“换基后目标函数值不变”的情况。极端情况下单纯形法可能陷入循环永远跳不出某个顶点。实际手算时遇到退化最简单的处理方式是换一个进基变量。比如本来选检验数最大的x₁进基但发现比值有多个相同的最小值那就改选检验数第二大的x₂进基。大多数情况下这样就能跳出退化。对于计算机求解常用的策略是Bland法则每次选下标最小的非基变量进基选下标最小的基变量出基。这个规则理论上保证不会循环但收敛速度可能慢一些。实际软件中还会用扰动法、字典序法等更高效的反循环策略。5.2 大M法与两阶段法初始基变量不好找怎么办前面的例子中约束都是≤松弛变量天然就是初始基变量。但如果约束中有≥或松弛变量和剩余变量的系数可能是-1或0不能直接作为初始基变量。这时候需要引入人工变量。大M法的思路是给人工变量一个很大的负系数最大化问题强迫它们在最优解中取0。比如约束“x₁ x₂ ≥ 10”变成“x₁ x₂ - s₁ a₁ 10”其中a₁是人工变量目标函数中a₁的系数是-MM是一个很大的正数。如果最终a₁0说明原问题无可行解。两阶段法把求解分成两步第一阶段最小化人工变量之和如果最小值为0说明找到了原问题的一个基本可行解第二阶段从这个解出发用原目标函数继续迭代。两阶段法在数值上比大M法更稳定因为不需要选一个“很大”的M避免了数值溢出和精度问题。5.3 无界解与无可行解模型出问题的信号无界解意味着目标函数可以无限增大。在实际问题中这通常是因为漏掉了某个关键约束。比如生产问题中只考虑了人工和原料忘了考虑市场需求上限模型就会建议“生产无限多”。遇到无界解第一件事是回去检查约束是否完整。无可行解意味着约束条件互相矛盾。比如同时要求“x₁ x₂ ≥ 10”和“x₁ x₂ ≤ 5”那不管怎么取值都满足不了。实际建模中无可行解往往是因为约束写得太紧或者不同部门给出的约束互相冲突。这时候需要和业务方沟通看哪些约束可以放宽。5.4 手算练习的选表技巧手算单纯形表时选哪个变量进基、哪个出基直接影响迭代次数。我的经验是进基变量优先选检验数最大的这样目标函数改进最快。如果两个检验数差不多选系数列更“稀疏”的那个行变换计算量小。出基变量严格按最小比值法则不要凭感觉选否则可能破坏可行性。每次迭代后检查Z行如果所有检验数≤0立刻停止不要多算。还有一个偷懒技巧如果某个非基变量的检验数是0说明存在多重最优解。这时候可以停止迭代当前解已经是最优的只是还有另一个顶点也是最优的。多重最优解在实际中意味着资源分配有多个同样好的方案可以根据其他非量化因素做最终决策。6. 从手算到代码用Python验证你的单纯形表6.1 用scipy快速求解线性规划手算练的是理解实际工作中没人真的手算。Python的scipy库提供了现成的线性规划求解器几行代码就能搞定。from scipy.optimize import linprog # 最大化 300x1 200x2linprog默认求最小化所以取负 c [-300, -200] # 不等式约束 A_ub * x b_ub A_ub [[2, 1], [1, 3]] b_ub [120, 150] # 变量下界 bounds [(0, None), (0, None)] result linprog(c, A_ubA_ub, b_ubb_ub, boundsbounds, methodhighs) print(f最优解: x1{result.x[0]:.2f}, x2{result.x[1]:.2f}) print(f最大利润: {-result.fun:.2f})运行结果应该是x142, x236最大利润19800和手算完全一致。methodhighs是scipy推荐的高效求解器底层用的不是单纯形法而是内点法但对于小规模问题结果一样。6.2 用单纯形表逻辑自己写一个求解器如果你想验证自己的手算过程可以写一个简单的单纯形法求解器。核心逻辑就是前面讲的构建初始表、选进基变量、选出基变量、行变换、检查检验数。import numpy as np def simplex(c, A, b): m, n A.shape # 添加松弛变量 A_aug np.hstack([A, np.eye(m)]) c_aug np.hstack([c, np.zeros(m)]) # 初始基变量索引 basis list(range(n, n m)) while True: # 计算检验数 cB c_aug[basis] sigma c_aug - cB A_aug # 检查是否最优 if np.all(sigma 1e-8): break # 选进基变量 enter np.argmax(sigma) # 最小比值法则 ratios [] for i in range(m): if A_aug[i, enter] 1e-8: ratios.append(b[i] / A_aug[i, enter]) else: ratios.append(np.inf) if min(ratios) np.inf: return 无界解 leave np.argmin(ratios) # 行变换 pivot A_aug[leave, enter] A_aug[leave] / pivot b[leave] / pivot for i in range(m): if i ! leave: factor A_aug[i, enter] A_aug[i] - factor * A_aug[leave] b[i] - factor * b[leave] basis[leave] enter # 提取解 x np.zeros(n m) for i, idx in enumerate(basis): x[idx] b[i] return x[:n], c_aug x # 测试 c np.array([300, 200]) A np.array([[2, 1], [1, 3]]) b np.array([120, 150]) x, z simplex(c, A, b) print(f最优解: x1{x[0]:.2f}, x2{x[1]:.2f}) print(f最大利润: {z:.2f})这个求解器虽然简陋但完整实现了单纯形法的核心逻辑。你可以用它来验证手算的每一步也可以修改输入看看不同情况下的输出。6.3 用对偶问题做交叉验证线性规划有一个重要性质每个原问题都有一个对应的对偶问题。原问题是最大化对偶就是最小化原问题的约束系数矩阵转置后变成对偶的约束系数矩阵原问题的目标系数变成对偶的右端项原问题的右端项变成对偶的目标系数。前面例子的对偶问题是最小化 120y₁ 150y₂约束为 2y₁ y₂ ≥ 300y₁ 3y₂ ≥ 200y₁, y₂ ≥ 0。对偶问题的最优解y₁140, y₂20正好是原问题中松弛变量的影子价格。强对偶定理告诉我们原问题和对偶问题的最优目标函数值相等。所以如果你算完原问题可以顺手算一下对偶问题如果两个目标值相等说明计算大概率没错。对偶问题还有一个实际意义原问题是“给定资源最大化利润”对偶问题是“给定利润目标最小化资源成本”。两者从不同角度描述同一个经济问题互为镜像。7. 学完这一章之后下一步往哪走线性规划和单纯形法是运筹学的门槛跨过去之后你会发现后面很多内容都是它的变体和扩展。整数规划是在线性规划基础上要求变量取整数比如生产件数不能是小数目标规划是处理多个目标之间的权衡比如既要利润高又要污染少网络流问题是线性规划在图论中的特例运输问题、指派问题都是它的经典应用。如果你想把这一章学扎实我的建议是找三到五个实际问题亲手建模、手算单纯形表、再用代码验证。手算的过程虽然繁琐但能让你真正理解检验数的含义、最小比值法则的作用、退化是怎么发生的。这些直觉在后续学习对偶理论、敏感性分析、列生成法等高级内容时会反复用到。另外不要忽视建模这一环。很多人单纯形表算得很溜但拿到实际问题就不知道从哪下手。建模能力的提升没有捷径就是多练、多看别人的模型、多和业务方沟通。一个实用的技巧是先把所有能想到的变量和约束都列出来然后逐个问“这个变量真的可控吗”“这个约束真的必要吗”删掉冗余的补上遗漏的模型就慢慢清晰了。最后说一个我自己的体会线性规划最迷人的地方不是单纯形法的计算技巧而是它把“资源有限、目标明确”这类问题抽象成了一个统一的数学结构。一旦你习惯了这种抽象方式再看很多现实问题时脑子里会自动浮现出决策变量、目标函数、约束条件这三件套。这种思维方式的转变比学会任何具体算法都更有价值。