线性规划入门:从投资分配问题到Python/MATLAB实战求解

📅 发布时间:2026/8/28 4:50:57
线性规划入门:从投资分配问题到Python/MATLAB实战求解 1. 从一道“分钱”题说起线性规划到底是什么最近在整理资料翻到了几年前带学生参加数学建模竞赛时的一道经典练习题。题目很简单假设你手头有100万资金需要投资到三个项目里。项目A预计年回报率8%但风险较高你不想投超过总资金的40%项目B回报率5%比较稳健你想至少投20万项目C回报率12%是政府扶持项目但要求投资额不低于30万。问怎么分配这100万才能让一年的总回报最高这问题听起来像是个小学应用题但稍微一想就会发现它没那么简单。你不能光盯着回报率最高的项目C猛投因为还有各种限制条件卡着你。当时很多学生第一反应是“凑数”或者凭感觉试结果要么算得头疼要么离最优解差得远。其实这个“分钱”问题就是线性规划Linear Programming, LP最经典的缩影。线性规划不是什么高深莫测的数学魔法它就是一套处理这类“在有限资源资金、时间、人力和一堆规则投资上限、最低额度约束下寻找某个目标利润最大、成本最小最佳方案”的数学方法。它的核心思想就两条第一你要优化的目标比如总回报和所有的限制条件都能用线性的数学式子一次方程或不等式来表达第二你要找的解决方案是满足所有限制的、并且让目标达到最优的那个点。为什么线性规划在数学建模里地位这么高因为它太“接地气”了。从工厂的生产排期用有限的机器、人力、原材料生产哪些产品、各生产多少利润最高到物流公司的运输路线规划从多个仓库往多个超市送货怎么安排车辆和路线总运费最低再到互联网公司的广告投放预算分配底层逻辑都是线性规划。它把现实中复杂的优化问题抽象成了一个可计算、可求解的数学模型。掌握了它你就相当于拿到了一把解开一大类现实优化问题的钥匙。2. 线性规划模型的“标准照”三要素拆解一个完整的线性规划模型就像一个人的标准证件照必须包含三个核心要素决策变量、目标函数和约束条件。我们拿刚才的投资问题来“对号入座”。2.1 决策变量问题的“方向盘”决策变量就是你手里能调节的“旋钮”。在这个投资问题里你能决定的就是投给项目A、B、C各多少钱。所以我们很自然地定义三个变量设x_A为投资到项目A的金额万元设x_B为投资到项目B的金额万元设x_C为投资到项目C的金额万元这些x就是我们的决策变量。它们必须是非负的实数因为你不可能投资负的钱这是线性规划的一个隐含前提。定义好变量问题就从模糊的“怎么分配”变成了清晰的“求x_A, x_B, x_C的值”。2.2 目标函数我们要的“最优”目标函数就是你最终想达到的那个“好”的标准并且要用数学公式表达出来。在这个例子里我们的目标是“总回报最高”。总回报怎么算就是每个项目的投资额乘以它的回报率然后加起来。 所以目标函数是最大化 Z 0.08*x_A 0.05*x_B 0.12*x_C这里的Z就代表总回报单位是万元。最大化这个词很关键它指明了我们的优化方向。如果是成本问题目标函数可能就是最小化。2.3 约束条件现实的“紧箍咒”光有目标不行现实会给你套上各种“紧箍咒”这就是约束条件。它们同样需要用线性等式或不等式来表示。资金总量约束总投资额就是100万。x_A x_B x_C 100这是一个等式约束项目A投资上限不能超过总资金的40%即x_A 0.4 * 100 40项目B投资下限至少投20万即x_B 20项目C投资下限至少投30万即x_C 30非负约束虽然简单但绝不能忘x_A 0, x_B 0, x_C 0把这三要素放在一起一个标准的线性规划模型就诞生了最大化 Z 0.08*x_A 0.05*x_B 0.12*x_C 满足 x_A x_B x_C 100 x_A 40 x_B 20 x_C 30 x_A, x_B, x_C 0看到没所有式子都是变量的一次项这就是“线性”的含义。这个模型干净利落把现实问题转化成了数学语言接下来就是求解了。注意在实际建模中定义变量是一门艺术。变量定义得好模型就简洁清晰定义得不好模型会变得复杂难解。比如如果问题涉及“是否选择”某个项目0-1决策你可能需要引入0-1变量这就进入了整数规划的范畴是线性规划的进阶版。3. 图解与求解从二维洞察到通用算法对于只有两个决策变量的简单线性规划我们可以在平面直角坐标系上把它画出来非常直观。假设我们把上面的问题简化只投资A和B两个项目x_C0约束也相应调整。那么模型变成最大化 Z 0.08*x_A 0.05*x_B 满足 x_A x_B 100 假设总资金可不用完 x_A 40 x_B 20 x_A, x_B 03.1 图解法在可行域里找“最优点”第一步画约束。把每个不等式当成等式直线画出来然后判断取哪一侧。x_A x_B 100是一条直线因为不等式是“小于等于”所以取这条直线左下方的区域。x_A 40是一条竖线取左侧区域。x_B 20是一条横线取上方区域。再加上x_A0和x_B0我们被限制在第一象限。所有这些约束条件所描述的区域的交集就是一个凸多边形区域称为“可行域”。可行域里的每一个点都代表一个满足所有约束的、可行的投资方案。第二步找最优。目标函数Z 0.08*x_A 0.05*x_B可以改写为x_B (Z/0.05) - (0.08/0.05)*x_A。这是一簇斜率固定的平行线Z的值就是这条线在x_B轴上的截距。我们的目标是让Z最大也就是让这条线的截距最大。怎么做拿一把尺子代表这簇平行线在可行域这个多边形里平移。你会发现当这条线平移到刚刚好擦过可行域的一个顶点时截距达到最大再往外就离开可行域了。这个顶点就是最优解。在这个例子里最优点很可能是直线x_A40和x_Ax_B100的交点即 (40, 60)。代入目标函数得到最大回报Z 0.08*40 0.05*60 3.2 3.0 6.2万元。图解法的核心启示对于线性规划如果存在最优解那么它一定出现在可行域的某个“顶点”或称“极点”上。这个结论对于高维空间变量更多依然成立它是单纯形法的理论基础。3.2 单纯形法攀登顶点的“智能登山者”实际问题动辄几十、上百个变量没法画图了。这时候就要靠算法。单纯形法Simplex Method是求解线性规划最经典、最核心的算法没有之一。你可以把它想象成一个在可行域这个高维多面体的“顶点”之间跳跃的智能登山者。初始化算法首先找到一个可行的起点顶点这本身有时就是个技术活可以通过引入“人工变量”解决。迭代优化在这个顶点算法会环顾四周看看沿着哪条棱边走能让目标函数Z值增长得最快对于最大化问题。这个判断是通过计算所谓的“检验数”来完成的。移动选好方向后算法就沿着这条棱走到下一个顶点。因为可行域是凸的这个移动保证Z值不会下降。终止当走到一个顶点发现四周的任何一条棱都无法让Z值再增加时恭喜你已经站在了最高点最优解。单纯形法之所以强大是因为它通常只需要遍历少数几个顶点就能找到最优解而不是傻傻地枚举所有顶点。虽然理论上它的最坏情况复杂度是指数级的但在绝大多数实际应用中它表现得非常高效。现在你用的各种求解器如MATLAB的linprogPython的SciPy.optimize.linprog默认的内核算法大多都是单纯形法或其变种。实操心得对于初学者我强烈建议不要一上来就敲代码。哪怕变量很多也试着在纸上列出完整的数学模型决策变量、目标函数、约束条件。这个过程能帮你理清逻辑避免把问题本身想错。模型列对了调用求解器就是一行命令的事模型列错了解出来也是垃圾。4. 从理论到代码用Python和MATLAB实战求解模型建好了算法原理也懂了接下来就是让计算机干活。这里我用开头的完整三变量投资问题作为例子分别演示如何在Python和MATLAB中求解。4.1 Python方案SciPy库简洁明了Python里用SciPy库的optimize.linprog函数是最常见的选择。需要注意的是linprog默认是求解最小化问题并且约束形式是A_ub * x b_ub和A_eq * x b_eq。所以我们需要把我们的最大化问题稍作转换。第一步模型转换我们的模型是最大化 Z 0.08*x_A 0.05*x_B 0.12*x_C等价于最小化 -Z -0.08*x_A -0.05*x_B -0.12*x_C这样目标函数系数向量c [-0.08, -0.05, -0.12]第二步整理约束矩阵我们需要把不等式约束都写成的形式。x_B 20等价于-x_B -20x_C 30等价于-x_C -30x_A 40已经是形式。所以不等式约束组为[ 1, 0, 0] [x_A] [40] (x_A 40) [ 0, -1, 0] * [x_B] [-20] (x_B 20 - -x_B -20) [ 0, 0, -1] [x_C] [-30] (x_C 30 - -x_C -30)即A_ub [[1,0,0], [0,-1,0], [0,0,-1]],b_ub [40, -20, -30]等式约束只有一个x_A x_B x_C 100即A_eq [[1,1,1]],b_eq [100]变量有非负约束linprog默认x 0所以不用额外指定。第三步编写代码import numpy as np from scipy.optimize import linprog # 目标函数系数求最小化所以取负 c [-0.08, -0.05, -0.12] # 不等式约束矩阵 A_ub * x b_ub A_ub [[1, 0, 0], # x_A 40 [0, -1, 0], # -x_B -20 - x_B 20 [0, 0, -1]] # -x_C -30 - x_C 30 b_ub [40, -20, -30] # 等式约束 A_eq * x b_eq A_eq [[1, 1, 1]] b_eq [100] # 变量边界默认就是0到正无穷这里显式写出更清晰 x_bounds [(0, None), (0, None), (0, None)] # 调用求解器 res linprog(c, A_ubA_ub, b_ubb_ub, A_eqA_eq, b_eqb_eq, boundsx_bounds, methodhighs) # 输出结果 if res.success: print(优化成功) print(f最优解x_A {res.x[0]:.2f} 万元, x_B {res.x[1]:.2f} 万元, x_C {res.x[2]:.2f} 万元) print(f最大年回报为{-res.fun:.4f} 万元) # 注意取负变回最大值 else: print(优化失败, res.message)运行这段代码你会得到结果x_A 40.0, x_B 30.0, x_C 30.0最大回报Z 0.08*40 0.05*30 0.12*30 3.2 1.5 3.6 8.3万元。这个解符合直觉在满足B、C最低投资额后把剩余的所有额度都给了回报率最高的A项目但没超过其上限。4.2 MATLAB方案linprog函数直截了当MATLAB的优化工具箱同样强大其linprog函数语法与SciPy类似但更贴近线性规划的标准形式。% 目标函数系数求最小值所以原最大化的系数取负 f [-0.08; -0.05; -0.12]; % 不等式约束 A*x b % 注意MATLAB中需要直接写成 形式 A [1, 0, 0; % x_A 40 0, -1, 0; % -x_B -20 - x_B 20 0, 0, -1]; % -x_C -30 - x_C 30 b [40; -20; -30]; % 等式约束 Aeq*x beq Aeq [1, 1, 1]; beq 100; % 变量下界和上界 (lb x ub) lb [0; 0; 0]; % 下限为0 ub []; % 上限无用空矩阵表示正无穷 % 调用linprog求解 [x, fval, exitflag, output] linprog(f, A, b, Aeq, beq, lb, ub); % 输出结果 if exitflag 0 fprintf(优化成功\n); fprintf(最优解x_A %.2f, x_B %.2f, x_C %.2f\n, x(1), x(2), x(3)); fprintf(最大年回报为%.4f\n, -fval); % fval是最小化目标函数值取负得最大值 else fprintf(优化失败。\n); disp(output.message); end运行后你将得到与Python完全相同的结果。避坑指南初次使用求解器最容易出错的地方就是约束条件的符号和格式。务必牢记你的“最大化”问题在输入求解器时通常要转化为“最小化”问题目标函数系数取负。仔细核对每个不等式是“≤”还是“≥”并统一转换成求解器要求的形式通常是“≤”。一个符号错了整个可行域就变了。等式约束一定要确保是严格的“”并且没有重复或矛盾的等式。养成在求解后验证解的习惯把求得的x值代回每一个原始约束条件看看是否都满足。这是检查模型是否构建正确的最后一道保险。5. 数学建模竞赛中的线性规划不止于求解在数学建模竞赛中线性规划很少会以这种“裸题”的形式出现。它更多时候是作为一个核心模块嵌套在一个更复杂的问题中。评委看重的不仅仅是你能否调用linprog求出答案更是你如何将一个错综复杂的实际问题抽象提炼成一个线性规划模型的能力。5.1 经典赛题思路拆解我们来看一个简化版的“生产计划”问题它融合了资源限制、市场需求和利润最大化是国赛、美赛的常客。问题描述某工厂生产两种产品I和II。生产每件产品I需耗用原料A 2kg、原料B 1kg占用设备1台时利润为6千元。生产每件产品II需耗用原料A 1kg、原料B 2kg占用设备2台时利润为8千元。工厂每日可用原料A最多80kg原料B最多100kg设备台时最多70。市场调查显示产品I每日最大需求量为40件。问如何安排每日生产计划使总利润最大建模步骤定义决策变量这很直接。设x1为每日生产产品I的件数x2为每日生产产品II的件数。构建目标函数总利润最大化。Max Z 6*x1 8*x2(单位千元)列出约束条件原料A约束2*x1 1*x2 80(生产两种产品消耗的A原料总和不超过80kg)原料B约束1*x1 2*x2 100设备台时约束1*x1 2*x2 70市场需求约束x1 40非负约束x1 0, x2 0这个模型清晰明了。求解后你可能会得到x130, x220最大利润Z340千元。但竞赛中问题往往会更进一步。5.2 灵敏度分析当条件变化时计划还最优吗这是线性规划在建模中真正的精髓所在也是论文拿高分的关键。灵敏度分析研究的是模型中的参数如利润系数、资源限量发生微小变化时最优解会如何变化它有多稳定影子价格比如我们想知道原料A增加1kg总利润能增加多少这个增加的量就是原料A的“影子价格”。它代表了该资源在最优生产计划下的边际价值。如果影子价格很高说明该资源是瓶颈购买或获取更多该资源能显著提升利润如果为0说明该资源已有富余再增加也没用。求解器如MATLAB的linprog的输出里通常会包含这个信息。目标函数系数范围产品I的利润在什么范围内波动时当前的最优生产组合生产I和II的比例不会改变这能告诉你市场价格的波动对你的生产计划有多大的“免疫力”。约束右端项范围原料B的供应量在什么范围内变化时当前起作用约束即“紧”约束或称“有效”约束的集合不会改变这有助于评估供应链的稳定性。在论文中你需要将这些分析结果用文字和表格清晰地表述出来并给出管理建议。例如“根据影子价格分析设备台时是当前最主要的瓶颈其影子价格为2千元/台时。建议优先考虑通过加班或租赁方式增加设备可用时间这将比增加原料采购带来更高的利润回报。”5.3 模型扩展与变体实际问题很少是纯线性的。你需要学会识别和处理线性规划的“近亲”。整数规划如果上面问题中产品必须整箱生产每箱10件或者你需要决定是否开设某条生产线是或否的0-1决策变量就必须取整数。这时就需要用到整数规划。虽然求解更复杂但建模思路一脉相承。多目标规划工厂可能不仅想利润最大还想能耗最低、碳排放最少。多个目标之间往往矛盾这就需要引入权重或者寻找“帕累托最优”解集。动态规划/随机规划如果生产计划是多阶段的或者资源供应、市场需求是不确定的模型就会变得更加复杂。竞赛论文写作要点在论文的模型建立部分一定要清晰地写出“设…为决策变量”然后分点列出目标函数和所有约束条件并附上必要的文字说明。在模型求解部分除了给出代码和结果更重要的是对结果进行解释“这意味着工厂应每日生产I产品30件II产品20件可获得最大利润34万元。” 最后灵敏度分析部分往往是区分优秀论文和普通论文的地方务必花篇幅深入讨论。6. 常见陷阱、误区与高级技巧掌握了基本流程并不意味着就能用好线性规划。下面这些坑我和我的学生们都曾踩过。6.1 误区一盲目追求“最优解”忽略模型假设线性规划模型是现实的简化它的“最优解”是建立在你的假设之上的。如果你的假设偏离实际那么解再“优”也没用。案例一个经典的运输问题模型目标是总运费最低。模型最优解可能是让A仓库长途跋涉给隔壁市的超市送货而让B仓库穿越半个国家去送。从纯数学上看这或许满足了“运费最低”可能因为A到隔壁市运费极高而B远程运输有极低的批量折扣。但这显然不合理因为它忽略了运输时间、车辆调度复杂度、合作关系等隐性成本。对策建模后一定要回过头审视这个“最优解”在现实中是否可行、是否合理。必要时需要在模型中增加约束来体现这些现实考量比如增加单次运输距离上限、或规定某些仓库-超市的配对关系。6.2 误区二变量定义不当导致模型臃肿或错误这是新手最容易出错的地方。错误案例在排班问题中如果你为“员工i在日期d的班次s是否上班”定义一个0-1变量x_ids那么对于7天、3班倒、10个员工的情况你会有10*7*3210个变量。虽然也能解但模型庞大。更优做法有时换一种定义方式能极大简化模型。比如你可以定义y_id为员工i在日期d被安排的班次类型如1早班2中班3晚班0休息。这样变量数减少为10*770个但约束条件的写法会发生变化。选择哪种取决于约束条件更容易用哪种变量来表达。6.3 高级技巧利用对偶理论理解问题每一个线性规划问题称为原问题都有另一个与之相伴的线性规划问题称为它的对偶问题。这不仅仅是数学上的优美对称更有深刻的实际意义。原问题是在资源限制下如何组织生产使利润最大。对偶问题是如果有人想购买你的所有资源他应该如何给每种资源定价即影子价格使得你的总资源价值最小但同时定价不能低于你用这些资源生产所能获得的单位产品利润。对偶理论的价值提供另一种求解视角有时求解对偶问题比原问题更容易例如当原问题变量很多但约束很少时其对偶问题约束多变量少可能更好解。深化经济解释对偶变量的最优解就是原问题资源的影子价格这是进行灵敏度分析和经济决策的黄金指标。验证解的正确性根据强对偶定理如果原问题和对偶问题都有最优解那么它们的最优目标函数值相等。这可以作为一个强大的验算工具。6.4 求解失败怎么办诊断与调试当你兴冲冲地运行代码却得到“无可行解”或“无界解”的报错时别慌。无可行解这意味着你的约束条件互相矛盾画不出公共的可行域。比如你既要求x1 x2 100又要求x1 30且x2 30。这显然不可能同时满足。排查方法逐一检查或放松约束条件尤其是那些上下限。是不是某个下限设得比上限还高是不是等式约束太严格无界解这意味着在你的约束条件下目标函数可以朝着优化的方向无限增大对于最大化或减小对于最小化。比如你的目标是最大化x1 x2但约束只有x1 0, x2 0那x1和x2可以无限大利润也就无限大。这通常是因为你漏掉了关键的资源限制约束。排查方法检查是否所有消耗资源的约束都已正确添加。调试心法对于复杂模型我习惯用“分步验证法”。先构建一个极度简化的模型比如只保留核心约束和少数变量确保它能求解并得到合理结果。然后像搭积木一样一次只添加一个或一类约束每加一次就运行一次观察解的变化。如果某次添加后模型出错问题就出在新加的这部分约束上。这个方法虽然慢但能精准定位问题根源。线性规划是一座连接现实问题与数学优化的坚实桥梁。它不要求你具备多么高深的数学背景但需要你具备严谨的逻辑思维和将模糊需求转化为精确数学语言的能力。从看懂一道例题到亲手建出一个模型并用代码求解再到能对结果进行深入的经济解释这个过程本身就是一次完整的数学建模训练。下次当你再遇到“如何分配”“怎样安排”“最多/最少”这类问题时不妨先想想这能不能用一个线性规划模型来描述很多时候答案都是肯定的。