钢管切割问题
题目要求
某单位需要加工制作100套钢架,每套钢架长为2.9m,2.1m和1m的圆钢各一根。已知原料长为6.9m,问如何切割原材料可以使得使用的原材料最少
分析
考虑最简单的切割方法,准备100根原材料,每根原材料上面都截取2.9m,2.1m和1m的圆钢各一根,一共需要100根原材料,每根原材料剩下的钢管为0.9m,此时,我们对一根钢管可截取的圆钢数目进行枚举,写出所有可行的切割方案(同时要求剩余长度不超过1m),使用Python语言进行枚举,如下表所示。
2.9m 数量 | 2.1m 数量 | 1m 数量 | 合计长度 | 料头长度 |
---|---|---|---|---|
1 | 0 | 4 | 6.9 | 0.0 |
2 | 0 | 1 | 6.8 | 0.1 |
0 | 3 | 0 | 6.3 | 0.6 |
0 | 2 | 2 | 6.2 | 0.7 |
0 | 1 | 4 | 6.1 | 0.8 |
0 | 0 | 6 | 6.0 | 0.9 |
1 | 1 | 1 | 6.0 | 0.9 |
可以得出,一共有七种切割方案
具体枚举计算代码
# 钢架的长度
lengths = [2.9, 2.1, 1.0]
# 原材料长度
material_length = 6.9
# 枚举所有的切割方式
cutting_patterns = []
for i in range(0, int(material_length / lengths[0]) + 1):
for j in range(0, int(material_length / lengths[1]) + 1):
for k in range(0, int(material_length / lengths[2]) + 1):
total_length = i * lengths[0] + j * lengths[1] + k * lengths[2]
leftover = material_length - total_length
if total_length <= material_length and leftover <1:
cutting_patterns.append((i, j, k, total_length, leftover))
# 按照料头长度从小到大排序
cutting_patterns.sort(key=lambda x: x[4])
# 打印结果表格
print("2.9m 数量\t2.1m 数量\t1m 数量\t合计长度\t料头长度")
for pattern in cutting_patterns:
i, j, k, total_length, leftover = pattern
print(f"{i}\t\t{j}\t\t{k}\t\t{total_length:.1f}\t\t{leftover:.1f}")
模型的建立
为了求出使用原材料的最小值,假设按照方案A-G切割的原材料数量分别为
x
i
(
i
=
1
,
2
,
3
…
,
7
)
x_i(i=1,2,3\dots,7)
xi(i=1,2,3…,7) ,根据上表的数据我们可以建立以下线性整数规划模型,目标函数
min
∑
i
=
1
7
x
i
\min\sum_{i=1}^{7}x_{i}
mini=1∑7xi
约束条件
-
得到2.9m的钢管的数目大于等于100根
x 1 + 2 x 2 + x 7 ≥ 100 x_1+2x_2+x_7\geq100 x1+2x2+x7≥100 -
得到2.1m的钢管数目大于等于100
3 x 3 + 3 x 4 + x 5 + x 7 ≥ 100 3x_3+3x_4+x_5+x_7\geq100 3x3+3x4+x5+x7≥100 -
得到1m的钢管数目大于等于100
4 x 1 + x 2 + 2 x 4 + 4 x 5 + 6 x 6 + x 7 ≥ 100 4x_1+x_2+2x_4+4x_5+6x_6+x_7\geq100 4x1+x2+2x4+4x5+6x6+x7≥100 -
x i x_{i} xi为整非负整数
x i 是非负整数 ( i = 1 , 2 , ⋯ , 7 ) x_i\text{是非负整数}(i=1,2,\cdots,7) xi是非负整数(i=1,2,⋯,7)
建立完整的优化模型
min
∑
i
=
1
7
x
i
s
.
t
.
{
x
1
+
2
x
2
+
x
7
≥
100
3
x
3
+
2
x
4
+
x
5
+
x
7
≥
100
4
x
1
+
x
2
+
2
x
4
+
4
x
5
+
6
x
6
+
x
7
≥
100
x
i
是非负整数
(
i
=
1
,
2
,
⋯
,
7
)
\min\sum_{i=1}^{7}x_{i}\\ \left.s.t.\quad\left\{\begin{array}{l}x_1+2x_2+x_7\geq100\\3x_3+2x_4+x_5+x_7\geq100\\4x_1+x_2+2x_4+4x_5+6x_6+x_7\geq100\\x_i\text{是非负整数}(i=1,2, \cdots,7)\end{array}\right.\right.
mini=1∑7xis.t.⎩
⎨
⎧x1+2x2+x7≥1003x3+2x4+x5+x7≥1004x1+x2+2x4+4x5+6x6+x7≥100xi是非负整数(i=1,2,⋯,7)
模型的求解
这里使用Python语言的pulp库来解决这个线性规划问题,目标是最小化资源的使用量。具体的步骤
-
定义线性规划问题
my_tube = pulp.LpProblem('tube', LpMinimize)
作用: 这行代码定义了一个名为
my_tube
的线性规划问题,其中'tube'
是问题的名称,LpMinimize
表示这是一个最小化问题。也就是说,目标是找到变量的值,使得目标函数的值最小。 -
定义决策变量
x1 = LpVariable('x1', lowBound=0, cat=LpInteger) x2 = LpVariable('x2', lowBound=0, cat=LpInteger) x3 = LpVariable('x3', lowBound=0, cat=LpInteger) x4 = LpVariable('x4', lowBound=0, cat=LpInteger) x5 = LpVariable('x5', lowBound=0, cat=LpInteger) x6 = LpVariable('x6', lowBound=0, cat=LpInteger) x7 = LpVariable('x7', lowBound=0, cat=LpInteger)
这些代码定义了七个决策变量
x1
到x7
,每个变量代表使用某种切割方案的次数。lowBound=0
: 表示这些变量的下界是0,意味着这些变量不能取负值。cat=LpInteger
: 表示这些变量必须是整数,因为你不能有部分次的切割方案。 -
定义目标函数
my_tube += x1+x2+x3+x4+x5+x6+x7, 'obj'
这一行定义了目标函数,即将所有变量的和最小化。
目标函数是
x1 + x2 + x3 + x4 + x5 + x6 + x7
,表示使用的原材料的总数。这个表达式的最小值就是我们希望求解的结果。 -
定义约束条件
my_tube += x1 + 2*x2 + x7 >= 100, 'c1' my_tube += 3*x3+2*x4+x5+x7 >= 100, 'c2' my_tube += 4*x1+x2+2*x4+4*x5+6*x6+x7 >= 100
这些代码定义了线性规划问题的约束条件。
-
求解问题
my_tube.solve()
这行代码调用
PuLP
的求解器来解决这个线性规划问题。求解器会找到满足所有约束条件的变量值组合,使得目标函数最小化。
求解器运行后,你可以通过 value(x1)
、value(x2)
等方法来查看每个变量的最终值,从而知道使用了多少次每种切割方案,以及最小的原材料总数量是多少。
0-1背包问题
问题描述
0-1背包问题(0-1 Knapsack Problem)是经典的组合优化问题之一,常用于计算机科学、运筹学等领域。其核心是如何在给定容量的背包中选择物品,使得背包内物品的总价值最大化。0-1背包问题中的“0-1”表示每个物品只能选择一次,要么放入背包(选择1),要么不放入背包(选择0)。
假设有一个背包,容量为 𝐶(即背包能容纳的最大重量),同时有 𝑛个物品,每个物品 𝑖有两个属性:
- 重量 w i w_{i} wi
- 价值 v i v_{i} vi
你需要决定是否将每个物品放入背包中,从而使得在不超过背包容量的前提下,背包内物品的总价值最大化。
数学模型
设 x i x_{i} xi 为决策变量,表示第i个物品是否放入背包中:
x i = { 1 如果物品 i 被放入背包 0 如果物品 i 没有被放入背包 x_i=\begin{cases}1&\text{如果物品 }i\text{ 被放入背包}\\0&\text{如果物品 }i\text{ 没有被放入背包}&\end{cases} xi={10如果物品 i 被放入背包如果物品 i 没有被放入背包
目标最大化总价值
Maximize ∑ i = 1 n v i ⋅ x i \text{Maximize }\sum_{i=1}^nv_i\cdot x_i Maximize i=1∑nvi⋅xi
同时,背包的总重量不能超过C:
Subject to ∑ i = 1 n w i ⋅ x i ≤ C \text{Subject to}\sum_{i=1}^nw_i\cdot x_i\leq C Subject toi=1∑nwi⋅xi≤C
具体例子
下面解决一个背包问题
- 目标:最大化一个价值函数,这个价值函数是10个物品(‘x1’到‘x10’)的组合价值。
- 约束条件:背包的总容量不能超过30个单位
物品 | 价值 | 重量 |
---|---|---|
x1 | 540 | 6 |
x2 | 200 | 3 |
x3 | 180 | 4 |
x4 | 350 | 5 |
x5 | 60 | 1 |
x6 | 150 | 2 |
x7 | 280 | 3 |
x8 | 450 | 5 |
x9 | 320 | 4 |
x10 | 120 | 2 |
代码实现
from pulp import *
my_package = pulp.LpProblem('package', LpMaximize)
x1 = LpVariable('x1', cat=LpBinary)
x2 = LpVariable('x2', cat=LpBinary)
x3 = LpVariable('x3', cat=LpBinary)
x4 = LpVariable('x4', cat=LpBinary)
x5 = LpVariable('x5', cat=LpBinary)
x6 = LpVariable('x6', cat=LpBinary)
x7 = LpVariable('x7', cat=LpBinary)
x8 = LpVariable('x8', cat=LpBinary)
x9 = LpVariable('x9', cat=LpBinary)
x10 = LpVariable('x10', cat=LpBinary)
my_package += 540*x1+200*x2+180*x3+350*x4+60*x5+150*x6+280*x7+450*x8+320*x9+120*x10, 'obj'
my_package += 6*x1+3*x2+4*x3+5*x4+x5+2*x6+3*x7+5*x8+4*x9+2*x10<=30, 'c1'
my_package.solve()
value(my_package.objective)
for v in my_package.variables():
print(v.name,'=',v.varValue)
指派问题
问题背景
五名候选人组成4*100米混合泳接力
五个人的成绩
蝶泳 | 仰泳 | 蛙泳 | 自由泳 | |
---|---|---|---|---|
甲 | 66.8 | 75.6 | 87 | 58.6 |
乙 | 57.2 | 66 | 66.4 | 53 |
丙 | 78 | 67.8 | 84.6 | 59.4 |
丁 | 70 | 74.2 | 69.6 | 57.2 |
戊 | 67.4 | 71 | 83.8 | 62.4 |
模型的建立
-
确定目标函数
根据题意,制定一个选择方案,使得最终的成绩最好(即所用时间最短)。建立目标函数:
min ∑ j = 1 4 ∑ i = 1 5 t i j x i j \min\sum_{j=1}^{4}\sum_{i=1}^{5}t_{ij}x_{ij} minj=1∑4i=1∑5tijxij其中 i i i表示所选择的队员, j j j表示所选择的泳姿, x i j x_{ij} xij为决策变量,当 x i j = 1 x_{ij}=1 xij=1时,队员 i i i参加第 j j j种泳姿,否则不参加, t i j t_{ij} tij表示队员 i i i参加第 j j j种泳姿的耗时。
-
约束条件
-
每人只能选一种泳姿
∑ j = 1 4 x j j ≤ 1 , i = 1 , 2 , 3 , 4 , 5 \sum_{j=1}^{4}x_{jj}\leq1,i=1,2,3,4,5 j=1∑4xjj≤1,i=1,2,3,4,5 -
每种泳姿都有一人选
∑ i = 1 5 x i j = 1 , j = 1 , 2 , 3 , 4 \sum_{i=1}^{5}x_{ij}=1,j=1,2,3,4 i=1∑5xij=1,j=1,2,3,4 -
x i j x_{ij} xij的取值只能是0和1
x i j ∈ { 0 , 1 } x_{ij}\in\{0,1\} xij∈{0,1}
-
-
优化模型的整合体现
min ∑ j = 1 4 ∑ i = 1 5 t i j x i j s t . { ∑ j = 1 4 x j j ≤ 1 , i = 1 , 2 , 3 , 4 , 5 ∑ i = 1 5 x i j = 1 , j = 1 , 2 , 3 , 4 x i j ∈ { 0 , 1 } \min\sum_{j=1}^{4}\sum_{i=1}^{5}t_{ij}x_{ij} \\ st.\begin{cases} \sum_{j=1}^{4}x_{jj}\leq1,i=1,2,3,4,5\\ \sum_{i=1}^{5}x_{ij}=1,j=1,2,3,4 \\x_{ij}\in\{0,1\} \end{cases} minj=1∑4i=1∑5tijxijst.⎩ ⎨ ⎧∑j=14xjj≤1,i=1,2,3,4,5∑i=15xij=1,j=1,2,3,4xij∈{0,1}
模型求解
解决方法,使用匈牙利算法,又称Kuhn-Munkres
算法,来解决二分图的最小权匹配问题。具体而言,代码利用了SciPy库中的linear_sum_assignment
函数来实现这一算法。
匈牙利算法是用于求解指派问题(Assignment Problem)的一种多项式时间算法。它特别适合解决成本矩阵为方阵的任务分配问题,但也可以处理矩阵为非方阵的情况(通过填充虚拟行或列)。算法的核心思想是通过对成本矩阵进行行和列的优化,最终找到一个使总成本最小的任务到资源分配方案
linear_sum_assignment
函数的实现,在Pyhton
语言中可以直接实现匈牙利算法,接受一个成本矩阵的输入,输出两个数:row_ind
和 col_ind
,它们分别表示任务(行)和资源(列)的最优分配索引。
row_ind
是任务的索引。col_ind
是资源的索引,与row_ind
相对应
生成配对关系
list(zip(row_ind,col_ind))
将任务和资源的索引配对在一起,并且返回一个包含所有配对的列表,这个列表表示最优的任务到资源的分配方式。
计算总成本
cost[row_ind, col_ind].sum()
提取所有的最优分配的成本,并计算这些成本的总和,sum函数对成本进行求和,得到最小化的目标函数值,也就是最优分配下的总成本。
具体代码
import numpy as np
from scipy.optimize import linear_sum_assignment
# 损耗矩阵
cost = np.array([[66.8, 75.6, 87, 58.6],
[57.2, 66, 66.4, 53],
[78, 67.8, 84.6, 59.4],
[70, 74.2, 69.6, 57.2],
[67.4, 71, 83.8, 62.4]])
row_ind, col_ind = linear_sum_assignment(cost)
row_ind
col_ind
# 目标成员坐标
list(zip(row_ind,col_ind))
# 目标函数值
cost[row_ind, col_ind].sum()
代码的应用场景
这段代码常用于解决分配问题,比如在多个任务需要分配给多个工人(或机器、资源等)的场景下,寻找最优的分配方式以最小化总成本。这在运输问题、作业调度、以及资源分配等实际应用中非常有用。