1. 运输问题:从理论到实战的“最后一公里”
如果你学过运筹学,或者正在准备相关考试,那么“运输问题”这个词你一定不陌生。它几乎是所有运筹学教材里必讲的章节,也是各类考试、面试中的常客。但不知道你有没有这种感觉:看教材例题时,感觉每一步都懂,公式也背得滚瓜烂熟,可一旦题目条件稍微变一变,或者数据量一大,就不知道从何下手了。这感觉就像学开车,在驾校里倒库移库练得挺好,真上了复杂路况,还是手忙脚乱。
运输问题,本质上就是解决“如何以最低的总成本,把货物从多个供应地(产地)运送到多个需求地(销地)”的数学规划问题。它听起来特别“接地气”,因为物流、供应链、生产调度,甚至是一些资源分配的场景,底层逻辑都是它。但恰恰是这种看似简单的模型,在实际应用中藏着不少“坑”。比如,当产销量不平衡时怎么办?当运输路线有容量限制时怎么处理?当目标不是求最小成本,而是求最大利润时,模型又该如何调整?
今天,我们就抛开教材上那些高度抽象化的例题,来一次深度实战。我会结合几个经典又“狡猾”的例题,不仅带你一步步推演求解过程,更重要的是,拆解题目背后设置的“陷阱”,分享我在多年学习和教学中总结出的、那些教材上不会写的“解题心法”。我们的目标很明确:不仅要会算,更要懂为什么这么算,以及遇到变种题目时,如何快速找到突破口。
2. 夯实基础:运输问题模型的核心要素与标准形式
在跳进题海之前,我们必须把“武器”检查一遍。很多同学解题出错,第一步就错在对模型的基本假设和标准形式理解不透彻。
2.1 模型的三要素与表格表达
一个标准的运输问题,离不开三个核心要素:
- 供应量(Supply):
m个产地A1, A2, ..., Am,每个产地的供应量为a_i(i=1,2,...,m)。这代表了“我们有多少货可以发”。 - 需求量(Demand):
n个销地B1, B2, ..., Bn,每个销地的需求量为b_j(j=1,2,...,n)。这代表了“市场需要多少货”。 - 单位运价(Unit Cost):从产地
Ai到销地Bj运输一个单位货物的成本为c_ij。这构成了模型的“价格表”,是目标函数的核心。
最直观的表达方式就是运价表与产销平衡表。我们通常画一个m行n列的表格,表格中间每个格子填写c_ij,右侧增加一列“产量”,下方增加一行“销量”。
例如,一个简单的2产地3销地问题:
| 运价/产销 | 销地B1 | 销地B2 | 销地B3 | 产量 |
|---|---|---|---|---|
| 产地A1 | 2 | 9 | 10 | 9 |
| 产地A2 | 1 | 3 | 4 | 5 |
| 销量 | 4 | 6 | 4 | 14 |
注意:这个表格不仅仅是数据的罗列。在后续用表上作业法(如最小元素法、伏格尔法)求初始解时,我们就是在这个表格上直接进行“分配”操作的。养成画表的习惯,是解题规范性的第一步。
2.2 产销平衡:模型的“入场券”
标准运输问题有一个黄金前提:总产量等于总销量。即:∑a_i = ∑b_j
这是我们能够应用表上作业法(包括最小元素法、位势法检验等)的基石。如果题目不满足这个条件,我们必须先进行“标准化”处理,这是第一个易错点。
- 产大于销(∑a_i > ∑b_j):我们虚构一个“虚拟销地”
Bn+1,其销量等于多余的产量(∑a_i - ∑b_j)。关键点在于,运到虚拟销地的单位运价c_i,n+1如何设定?- 如果目标是极小化总成本:通常设为
0。因为把货物“运到”虚拟销地,实际意味着货物在产地没运出去,不发生运输成本。 - 如果目标是极大化利润:则需谨慎,通常设为
-M(一个极大的负数,在极大化问题中等价于成本极高,迫使模型不选择此路径),或根据库存成本具体设定。
- 如果目标是极小化总成本:通常设为
- 销大于产(∑a_i < ∑b_j):我们虚构一个“虚拟产地”
Am+1,其产量等于不足的销量(∑b_j - ∑a_i)。同样,从虚拟产地运出的单位运价c_m+1,j如何设定?- 极小化成本问题:通常设为
0。表示需求未被满足,但模型允许这种“缺货”,且缺货成本为0。如果缺货有惩罚成本,则应设为惩罚值。 - 极大化利润问题:通常设为
-M,表示无法从“虚拟”产地获得货物来满足需求,除非支付极高成本。
- 极小化成本问题:通常设为
很多题目会在这里埋坑,比如给出一个明显不平衡的产销数据,却不提醒你。第一步没做标准化,后面全盘皆输。
2.3 数学模型:理解决策变量与约束
用数学语言表述,运输问题的模型如下:
决策变量:x_ij表示从产地i运往销地j的货物量。这是我们要求解的对象。
目标函数(最小化总成本): Min Z = ∑∑ c_ij * x_ij (对 i=1..m, j=1..n 求和)
约束条件:
- 供应约束:从每个产地运出的总量等于其产量。 ∑ x_ij = a_i (对每个产地 i)
- 需求约束:运到每个销地的总量等于其销量。 ∑ x_ij = b_j (对每个销地 j)
- 非负约束:运输量不能为负。 x_ij ≥ 0
这个模型是一个特殊的线性规划问题,其约束矩阵具有非常特殊的结构(每列只有两个1),这决定了它必然存在整数解(当产量和销量为整数时),并且可以用比单纯形法更高效的表上作业法求解。
3. 经典例题精讲(一):平衡型问题与表上作业法全流程
我们来看一个最标准的平衡型问题,并完整走一遍表上作业法的流程。
例题1:某公司有3个工厂(A1, A2, A3)生产同一种产品,4个销售点(B1, B2, B3, B4)销售该产品。各工厂产量、各销售点销量及单位产品运价如下表所示。问应如何调运,使总运费最小?
| 运价/产销 | B1 | B2 | B3 | B4 | 产量 |
|---|---|---|---|---|---|
| A1 | 3 | 11 | 3 | 10 | 7 |
| A2 | 1 | 9 | 2 | 8 | 4 |
| A3 | 7 | 4 | 10 | 5 | 9 |
| 销量 | 3 | 6 | 5 | 6 | 20 |
第一步:验证产销平衡总产量 = 7 + 4 + 9 = 20 总销量 = 3 + 6 + 5 + 6 = 20 平衡,可直接求解。
第二步:用最小元素法求初始基可行解
最小元素法的思想很直观:优先满足单位运价最小的路线。但操作时有严格步骤,避免出错。
- 在运价表中找到最小的运价
c_ij。这里是c_21 = 1(A2到B1)。 - 尽可能多地满足它。A2产量为4,B1销量为3,取最小值
min(4,3)=3。将3填入(A2, B1)格。此时B1销量已满足,划去B1列。操作心得:在表中直接画掉被满足的行或列,并在旁边用小字标注剩余产量/销量,可以极大减少错误。划掉B1列后,A2行剩余产量为
4-3=1。 - 在未划去的格子中再找最小运价。此时最小的是
c_23 = 2(A2到B3)。 - A2剩余产量为1,B3销量为5,取
min(1,5)=1。将1填入(A2, B3)格。此时A2产量已分配完,划去A2行。B3列剩余销量为5-1=4。 - 继续在剩余格子中找最小运价。现在最小的是
c_13 = 3和c_11 = 3(都是3)。这里就涉及到一个关键技巧:当最小运价不止一个时,优先选择能分配更多运量的那个。我们比较一下:- 选(A1, B3):A1产量7,B3剩余需求4,可分配
min(7,4)=4。 - 选(A1, B1):但B1列已被划去,不可选。 所以选(A1, B3),分配4。划去B3列(因为其需求被满足),A1行剩余
7-4=3。
- 选(A1, B3):A1产量7,B3剩余需求4,可分配
- 重复此过程。接下来最小运价是
c_32 = 4(A3到B2)。分配min(9,6)=6,划去B2列,A3行剩余9-6=3。 - 然后最小运价是
c_34 = 5(A3到B4)。分配min(3,6)=3,划去A3行,B4列剩余6-3=3。 - 最后只剩下A1行和B4列。A1剩余产量3,B4剩余销量3,正好将3填入(A1, B4)。
得到初始调运方案如下表(括号内为运量):
| 运价/产销 | B1 | B2 | B3 | B4 | 产量 |
|---|---|---|---|---|---|
| A1 | 3 | 11 | 3 (4) | 10 (3) | 7 |
| A2 | 1 (3) | 9 | 2 (1) | 8 | 4 |
| A3 | 7 | 4 (6) | 10 | 5 (3) | 9 |
| 销量 | 3 | 6 | 5 | 6 | 20 |
重要检查:基变量个数应为m+n-1 = 3+4-1 = 6。我们数一下有数字的格子:(A1,B3)、(A1,B4)、(A2,B1)、(A2,B3)、(A3,B2)、(A3,B4),正好6个,且不存在闭合回路。初始解正确。
第三步:用位势法(对偶变量法)检验当前解是否最优
这是表上作业法的核心,也是难点。位势法的目的是计算每个非基变量(空格)的检验数σ_ij。如果所有σ_ij ≥ 0(对于最小化问题),则当前解最优;否则,需要调整。
求位势
u_i和v_j:对于每个基变量(有运量的格)x_ij,满足方程u_i + v_j = c_ij。我们有无穷多组解,通常令u_1 = 0来启动计算。- 基格 (A1,B3):
u1 + v3 = 3=>0 + v3 = 3=>v3 = 3 - 基格 (A1,B4):
u1 + v4 = 10=>0 + v4 = 10=>v4 = 10 - 基格 (A2,B1):
u2 + v1 = 1,v1还不知道。 - 基格 (A2,B3):
u2 + v3 = 2=>u2 + 3 = 2=>u2 = -1 - 由
u2 = -1和 (A2,B1) 格:-1 + v1 = 1=>v1 = 2 - 基格 (A3,B2):
u3 + v2 = 4,v2还不知道。 - 基格 (A3,B4):
u3 + v4 = 5=>u3 + 10 = 5=>u3 = -5 - 由
u3 = -5和 (A3,B2) 格:-5 + v2 = 4=>v2 = 9
得到位势:
u = (0, -1, -5),v = (2, 9, 3, 10)- 基格 (A1,B3):
计算非基变量(空格)检验数:公式
σ_ij = c_ij - (u_i + v_j)。- (A1,B1):
σ_11 = 3 - (0+2) = 1 - (A1,B2):
σ_12 = 11 - (0+9) = 2 - (A2,B2):
σ_22 = 9 - (-1+9) = 1 - (A2,B4):
σ_24 = 8 - (-1+10) = -1 - (A3,B1):
σ_31 = 7 - (-5+2) = 10 - (A3,B3):
σ_33 = 10 - (-5+3) = 12
- (A1,B1):
判断:我们发现
σ_24 = -1 < 0。根据判别准则,检验数出现负值,说明当前解不是最优,可以让x_24(A2到B4)进入基变量来改进方案。
第四步:用闭回路法调整方案
寻找闭回路:以检验数为负的非基变量格(A2,B4)为起点,寻找一条由水平/垂直直线构成的、转角点均为基变量格的闭合回路。这条回路是唯一的。 回路为:(A2,B4) → (A2,B3) → (A1,B3) → (A1,B4) → (A2,B4)。
确定调整量 θ:在回路的偶数顶点(即第二个、第四个转角点)中,找出运量最小的值。本例中,偶数顶点是 (A2,B3) 的1和 (A1,B4) 的3,最小值为
θ = min(1, 3) = 1。调整运量:在回路的奇数顶点(起点为第一个奇数顶点)运量加上
θ,在偶数顶点运量减去θ。- (A2,B4)(起点,原为空):
0 + 1 = 1 - (A2,B3):
1 - 1 = 0(变为空格) - (A1,B3):
4 + 1 = 5 - (A1,B4):
3 - 1 = 2
- (A2,B4)(起点,原为空):
调整后的新方案为:
| 运价/产销 | B1 | B2 | B3 | B4 | 产量 |
|---|---|---|---|---|---|
| A1 | 3 | 11 | 3 (5) | 10 (2) | 7 |
| A2 | 1 (3) | 9 | 2 | 8 (1) | 4 |
| A3 | 7 | 4 (6) | 10 | 5 (3) | 9 |
| 销量 | 3 | 6 | 5 | 6 | 20 |
第五步:重复检验与调整
对新方案再次用位势法求检验数。
- 令
u1=0。 - (A1,B3):
0+v3=3=>v3=3 - (A1,B4):
0+v4=10=>v4=10 - (A2,B1):
u2+v1=1 - (A2,B4):
u2+10=8=>u2=-2 - 由
u2=-2和 (A2,B1):-2+v1=1=>v1=3 - (A3,B2):
u3+v2=4 - (A3,B4):
u3+10=5=>u3=-5 - 由
u3=-5和 (A3,B2):-5+v2=4=>v2=9
计算非基变量检验数:
- (A1,B1):
3-(0+3)=0 - (A1,B2):
11-(0+9)=2 - (A2,B2):
9-(-2+9)=2 - (A2,B3):
2-(-2+3)=1 - (A3,B1):
7-(-5+3)=9 - (A3,B3):
10-(-5+3)=12
所有检验数σ_ij ≥ 0。因此,当前解为最优解。
最优调运方案:
- A1 → B3: 5 单位
- A1 → B4: 2 单位
- A2 → B1: 3 单位
- A2 → B4: 1 单位
- A3 → B2: 6 单位
- A3 → B4: 3 单位
最小总运费:Z = 5*3 + 2*10 + 3*1 + 1*8 + 6*4 + 3*5 = 15 + 20 + 3 + 8 + 24 + 15 = 85
避坑指南:
- 初始解退化:在求初始解或调整时,如果同时划去一行和一列,会导致基变量个数少于
m+n-1,称为“退化”。此时必须在被同时划去的行或列中,选择一个空格填入“0”,并将其视为基变量(有数字的格),否则位势法无法计算。这个“0”的选取有讲究,最好选在单位运价较小的格子,有利于后续迭代。- 闭回路画法:调整时,闭回路必须且只能以基变量格为转角点(除了起点是空格外)。画错回路是调整失败的主要原因。一个检查技巧:回路上的每个转角点(除起点外)都必须是已有运量的格子,且回路是闭合的。
- 检验数计算错误:这是最常出错的地方。务必先求对位势
u_i和v_j。一个快速验证方法是:任选一个空格,用你求出的u_i和v_j计算c_ij - (u_i+v_j),如果结果与你用闭回路法算出的检验数(通过沿着该空格的假想回路,正负交替加减运价)不一致,说明位势求错了。
4. 经典例题精讲(二):不平衡问题、最大化问题与复杂约束
现实问题很少是标准平衡型。我们来看两个变种。
例题2(产大于销):已知运输问题的产销量及运价如下,求最优调运方案。
| 运价/产销 | B1 | B2 | B3 | 产量 |
|---|---|---|---|---|
| A1 | 2 | 9 | 10 | 9 |
| A2 | 1 | 3 | 4 | 5 |
| 销量 | 4 | 6 | 4 | 14 |
第一步:识别与标准化总产量 = 9 + 5 = 14 总销量 = 4 + 6 + 4 = 14?等等,仔细加一下:4+6+4=14。咦,平衡? 这里就是第一个陷阱:数据看似平衡,但其实是“产大于销”。因为总产量14,总销量也是14,但注意看,产量列的数字9和5,与销量行的数字4、6、4,总和都是14。这没问题啊?等等,我们重新审视表格:这是一个2行3列的问题。m=2, n=3。总产量a1+a2=9+5=14,总销量b1+b2+b3=4+6+4=14。数学上是平衡的。
但为什么题目暗示这是“产大于销”的变种呢?这可能是一个表述陷阱。原题可能意在考察“产量”和“销量”数字本身不等的情况。我们假设原题销量数据是(4, 5, 4),总和13,小于产量14。那么我们就需要引入虚拟销地B4,销量为1,运价为0。
为了演示,我们假设销量是 (4, 5, 4),总销量13 < 总产量14。那么标准化步骤如下:
- 增加虚拟销地 B4,销量 = 14 - 13 = 1。
- 从A1、A2到B4的运价设为0。
- 新的产销平衡表如下:
| 运价/产销 | B1 | B2 | B3 | B4(虚) | 产量 |
|---|---|---|---|---|---|
| A1 | 2 | 9 | 10 | 0 | 9 |
| A2 | 1 | 3 | 4 | 0 | 5 |
| 销量 | 4 | 5 | 4 | 1 | 14 |
第二步:求解此时就可以用标准的表上作业法求解了。最优解中,分配到虚拟销地B4的运量,就代表了对应产地未运出的库存量。例如,如果最优解中x_14=1,则表示A1有1单位产品没有运出,留在本地。
核心要点:处理不平衡问题的关键,在于准确判断是“产大于销”还是“销大于产”,并正确设置虚拟产地/销地的运价。对于最小化成本问题,虚拟路线的运价通常设为0,表示“不发生运输成本”。但务必注意题目是否有特殊说明,比如库存成本、缺货惩罚等,这些成本需要体现在虚拟路线的运价上。
例题3(最大化问题):下表给出了一个运输问题的运价表,但这里的“运价”实际上是单位利润。求使总利润最大的调运方案。
| 利润/产销 | B1 | B2 | B3 | 产量 |
|---|---|---|---|---|
| A1 | 6 | 3 | 5 | 40 |
| A2 | 4 | 7 | 2 | 30 |
| 销量 | 30 | 25 | 20 | 75 |
第一步:转化对于最大化问题,有两种标准处理方法:
- 差值法(效率法):找出全局最大利润
M = max(c_ij)。然后构造一个新的“损失”矩阵c'_ij = M - c_ij。将原最大化问题转化为对c'_ij的最小化问题。因为最小化总“损失”等价于最大化总利润。- 本例中
M = 7。 - 新运价表为:
损失/产销 B1 B2 B3 产量 A1 1 4 2 40 A2 3 0 5 30 销量 30 25 20 75 - 然后用表上作业法求这个损失表的最小化解,即为原利润表的最大化解。
- 本例中
- 检验数符号反转法:直接对利润表
c_ij用表上作业法求解,但最优性判别准则要反过来:所有非基变量的检验数σ_ij ≤ 0时,解最优。因为此时让任何非基变量入基(增加运量)都不会再增加总利润(检验数为负或零表示利润会减少或不变)。
第二步:求解(以差值法为例)验证产销平衡:40+30=30+25+20=75,平衡。 对“损失”表用最小元素法求初始解(过程略)。最优解(经过迭代)可能为:
- A1 → B1: 30
- A1 → B3: 10
- A2 → B2: 25
- A2 → B3: 5 (注意:这是一个可能的解,实际需迭代至最优)
第三步:回溯将解代入原利润表计算总利润:Z_max = 30*6 + 10*5 + 25*7 + 5*2 = 180 + 50 + 175 + 10 = 415。
避坑指南:
- 最大化问题最易错的就是最优性判别。如果用第二种方法(直接对利润表做),求检验数的公式不变
σ_ij = c_ij - (u_i+v_j),但判断时一定要记住:所有σ_ij ≤ 0才是最优。如果看到正的检验数,说明让那个非基变量入基还能增加利润,需要调整。很多同学会习惯性地套用最小化问题的σ_ij ≥ 0准则,导致答案完全错误。- 差值法中M的选取:
M必须大于等于原表中所有c_ij。通常直接取最大值即可。但要注意,转化后的“损失”表所有元素非负,符合运输问题的常规形式。
5. 经典例题精讲(三):含禁止运输路线与中转问题
运输路线可能因道路不通、政策限制等原因无法使用,这就是“禁止运输”或“断路”问题。另一种常见变体是“中转”或“转运”问题。
例题4(含禁止路线):在例题1的基础上,假设从工厂A2到销售点B1的路线因故无法使用,求此时的最优调运方案。
解法:大M法处理禁止路线,标准方法是将该路线的单位运价c_ij设为一个极大的正数M。在最小化问题中,M意味着成本无限高,模型会自动规避该路线,除非万不得已(在产销平衡且无其他路径可选时,才可能被迫使用,此时问题可能无可行解)。
所以,我们只需将原题中的c_21从1改为M,然后重新求解。求解过程中,在最小元素法找最小运价时,会自动忽略这个巨大的M。在最终的最优解中,x_21必然为0(基变量不会选它,因为检验数会很大)。
实操技巧:在手工计算时,M可以看作一个比表中所有实际数字都大得多的数。在比较运价大小时,任何实际数字都小于M。在计算检验数σ_ij = c_ij - (u_i+v_j)时,如果c_ij = M,那么σ_ij也会是一个很大的正数,肯定不会成为入基变量(对于最小化问题)。
例题5(中转运输问题):这是运输问题一个非常实用的扩展。假设货物不仅可以从产地直接运到销地,还可以先运到某个中转站(仓库),再从中转站运到销地。甚至允许产地之间、销地之间互相转运。
这类问题通常的解法是转化为扩大的标准运输问题。
- 将每个地点既视为产地,也视为销地。每个地点的“产量”等于其原始产量加上可能的中转容量(如果无限则为一个很大的数);“销量”等于其原始销量加上可能的中转发出量。
- 构造一个包含所有地点(原始产地、原始销地、中转站)的扩大的运价表。
- 直接运输的运价已知。
- 中转运输的运价 = 第一段运价 + 第二段运价。
- 同一个地点自己到自己的运价为0(表示不移动)。
- 如果两点间不允许直接运输,则运价为
M。
- 确定扩大量后的产销量。这是一个关键且易错点。通常设中转站的产量和销量为一个足够大的数(大于等于总运输量),表示其吞吐能力足够大。原始产地的销量设为0(或一个很小的数,表示允许自我留存),原始销地的产量设为0。
例如,有产地A1、A2,销地B1、B2,一个中转站T。
- 原始数据:A1产量10,A2产量20;B1销量15,B2销量15。
- 运价:A1→T=2, A2→T=3, T→B1=4, T→B2=5, A1→B1=9, A2→B2=8(其他直接路线不可用或很贵)。
- 转化:将A1, A2, T, B1, B2 都视为“地点”。构造5x5的运价表。其中:
- A1到T的运价=2,A2到T=3,T到B1=4,T到B2=5,A1到B1=9,A2到B2=8。
- A1到A1, A2到A2, T到T, B1到B1, B2到B2 运价=0。
- 其他不允许或未知的路线运价设为
M。
- 产销量设定:
- A1作为产地:产量=10;作为销地:销量=0(或一个很小的数,表示它自己可以“消费”自己的产品,即不运出)。
- A2作为产地:产量=20;销量=0。
- T作为产地:产量=总运输量(如30)或更大,表示其可提供的转运货物量(来自其他产地);作为销地:销量=总运输量(30),表示其可接收的待转运货物量。
- B1作为销地:销量=15;产量=0。
- B2作为销地:销量=15;产量=0。
- 总产量 = 10+20+30 = 60,总销量 = 0+0+30+15+15 = 60。平衡。
- 然后对这个扩大的平衡运输问题用表上作业法求解。解中,
x_A1T表示从A1运到T的量,x_TB1表示从T运到B1的量,等等。x_A1A1可能非零,表示A1的产品留在了A1(即未运出),这在允许库存的情况下是合理的。
深度解析:中转问题的转化思想,本质上是将“运输网络”拍平成了一个所有节点两两相连的完全图问题。通过设置合理的产销量和运价(尤其是自己到自己的0运价和不允许通行的M运价),让模型自己去选择是直接运输还是经过中转。这是运输问题模型灵活性的一个绝佳体现。手工计算此类问题非常繁琐,但理解其转化原理至关重要,因为这是用计算机求解复杂物流网络问题的基础建模思想。
6. 软件求解与实战检验:从手工到自动化
虽然掌握手工计算是理解原理的基础,但在实际工作中,面对几十上百个产地销地的问题,我们必然依赖软件。这里以最通用的Excel Solver(规划求解)和Python的pulp库为例,演示如何将上述理论付诸实践。
6.1 使用Excel Solver求解运输问题
Excel Solver是一个内置的强大工具,非常适合中小规模问题的建模和求解。
建立模型区域:
- 在一个区域(如A1:E4)输入运价表、产量和销量,与我们的手工表一致。
- 在另一个区域(如G1:J4)建立同样大小的“决策变量表”,存放待求的
x_ij,初始可设为0或空白。 - 在K列计算每个产地的实际发出量:
K2 = SUM(G2:J2),向下填充。这对应供应约束。 - 在第5行计算每个销地的实际收到量:
G5 = SUM(G2:G4),向右填充。这对应需求约束。 - 在某个单元格(如L5)计算总成本:
=SUMPRODUCT(G2:J4, A2:D4)。这里G2:J4是运量区域,A2:D4是运价区域。
配置Solver参数:
- 目标单元格:
$L$5(总成本)。 - 选择“最小值”。
- 可变单元格:
$G$2:$J$4(决策变量区域)。 - 约束条件:
$G$2:$J$4 >= 0(非负约束)$K$2:$K$4 = $E$2:$E$4(供应约束:实际发出量 = 产量)$G$5:$J$5 = $G$6:$J$6(需求约束:实际收到量 = 销量)。这里假设第6行存放销量数据。
- 求解方法:选择“单纯线性规划”。
- 目标单元格:
求解与解读:点击“求解”,Solver会找到最优解并填入
G2:J4区域。L5显示最小总成本。
Excel实战技巧:
- 整数解:运输问题本身有整数解性质,但如果你的模型有额外约束破坏了这种性质,可以在Solver约束中添加“$G$2:$J$4 = 整数”。
- 保存模型:对于经常要修改数据重新求解的问题,可以使用Solver的“保存模型”功能,将参数设置保存在一片单元格中,以后通过“加载模型”快速恢复。
- 敏感性报告:求解后生成敏感性报告,可以查看影子价格(对偶价格),即产量或销量每增加一个单位,总成本的变化量,这对于商务决策非常有价值。
6.2 使用Python PuLP库求解
对于更复杂、规模更大或需要集成到自动化流程的问题,编程求解是更优选择。PuLP是Python中一个用户友好的线性规划建模库。
import pulp # 定义问题:最小化总成本 prob = pulp.LpProblem('Transportation_Problem', pulp.LpMinimize) # 定义产地和销地 plants = ['A1', 'A2', 'A3'] markets = ['B1', 'B2', 'B3', 'B4'] # 供应量和需求量 supply = {'A1': 7, 'A2': 4, 'A3': 9} demand = {'B1': 3, 'B2': 6, 'B3': 5, 'B4': 6} # 运价表 (字典的字典) costs = { 'A1': {'B1': 3, 'B2': 11, 'B3': 3, 'B4': 10}, 'A2': {'B1': 1, 'B2': 9, 'B3': 2, 'B4': 8}, 'A3': {'B1': 7, 'B2': 4, 'B3': 10, 'B4': 5} } # 定义决策变量字典 routes = [(i, j) for i in plants for j in markets] vars = pulp.LpVariable.dicts("Route", (plants, markets), lowBound=0, cat='Continuous') # 定义目标函数 prob += pulp.lpSum([vars[i][j] * costs[i][j] for (i, j) in routes]), "Total_Transportation_Cost" # 添加供应约束 for i in plants: prob += pulp.lpSum([vars[i][j] for j in markets]) == supply[i], f"Supply_Constraint_{i}" # 添加需求约束 for j in markets: prob += pulp.lpSum([vars[i][j] for i in plants]) == demand[j], f"Demand_Constraint_{j}" # 求解问题 prob.solve(pulp.PULP_CBC_CMD(msg=False)) # 使用CBC求解器,关闭求解信息 # 打印结果 print(f"Status: {pulp.LpStatus[prob.status]}") print(f"Total Cost = {pulp.value(prob.objective)}\n") print("Optimal Transportation Plan:") for i in plants: for j in markets: if vars[i][j].varValue > 0: print(f"{i} -> {j}: {vars[i][j].varValue} units")运行这段代码,它会输出与我们在例题1中手工计算一致的最优解和总成本85。
编程求解心得:
- 灵活性:用代码建模,可以轻松处理不平衡问题(修改
supply/demand字典)、最大化问题(将LpMinimize改为LpMaximize,并相应修改costs为利润)、禁止路线(将对应costs[i][j]设为一个很大的数,如1e9)。- 扩展性:中转问题虽然建模复杂,但用代码构建扩大的产、销、运价字典,比手工画表更不易出错。
- 验证工具:当你手工求解一个复杂问题后,可以用这样一段简单的代码快速验证答案的正确性,这是提高学习效率的利器。
从手工推演到软件求解,我们完成了对运输问题从理论认知到实践落地的闭环。手工计算锻炼的是对模型机理的深刻理解,而软件工具则是解决实际规模问题的生产力。两者结合,才能真正掌握这个运筹学中的经典模型。