运筹学练习Python精解——动态规划
练习1
设国家拨给60万元投资,供四个工厂扩建使用,每个工厂扩建后的利润与投资额的大小有关,投资后的利润函数如下表所示,试给出收益最大的投资计划。
利润\投资 | 0 | 10 | 20 | 30 | 40 | 50 | 60 |
---|---|---|---|---|---|---|---|
0 | 20 | 50 | 65 | 80 | 85 | 85 | |
0 | 20 | 40 | 50 | 55 | 60 | 65 | |
0 | 25 | 60 | 85 | 100 | 110 | 115 | |
0 | 25 | 40 | 50 | 60 | 65 | 70 |
初始化:初始化一个动态规划表 f
和一个决策表 decision
。f
用于存储最大利润,decision
用于记录每个工厂的最佳投资决策。
递归计算:
- 对于每个工厂从1到4。
- 对于每个可能的投资金额 从0到60。
- 对于每个可能分配给当前工厂的投资金额 (以10为步长),使用递归关系计算最大利润,并记录最佳决策。
回溯最优解:
- 从决策表 decision
中回溯,找到每个工厂的最佳投资方案。
import numpy as np
# 表中的利润函数
g = np.array([
[0, 20, 50, 65, 80, 85, 85],
[0, 20, 40, 50, 55, 60, 65],
[0, 25, 60, 85, 100, 110, 115],
[0, 25, 40, 50, 60, 65, 70]
])
# 工厂数量和总投资
num_factories = 4
total_investment = 60
# 初始化DP表和决策表
f = np.zeros((num_factories + 1, total_investment + 1))
decision = np.zeros((num_factories + 1, total_investment + 1), dtype=int)
# 填充DP表和决策表
for i in range(1, num_factories + 1):
for j in range(total_investment + 1):
max_profit = 0
best_k = 0
for k in range(0, min(j, 60) + 1, 10): # 考虑以10为步长的投资
current_profit = f[i-1][j-k] + g[i-1][k//10]
if current_profit > max_profit:
max_profit = current_profit
best_k = k
f[i][j] = max_profit
decision[i][j] = best_k
# 回溯找到最优解
investment_plan = []
remaining_investment = total_investment
for i in range(num_factories, 0, -1):
invest_in_current_factory = decision[i][remaining_investment]
investment_plan.append(invest_in_current_factory)
remaining_investment -= invest_in_current_factory
investment_plan.reverse() # 反转投资计划,按工厂顺序输出
max_profit = f[num_factories][total_investment]
print(max_profit, investment_plan)
# 160.0 [20, 0, 30, 10]
练习2
求解下面背包问题的最优解。
def knapsack(values, weights, capacity):
n = len(values)
dp = [[0] * (capacity + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for w in range(capacity + 1):
if weights[i-1] <= w:
dp[i][w] = max(dp[i-1][w], dp[i-1][w-weights[i-1]] + values[i-1])
else:
dp[i][w] = dp[i-1][w]
# 回溯找到最优解
w = capacity
items_selected = [0] * n
for i in range(n, 0, -1):
if dp[i][w] != dp[i-1][w]:
items_selected[i-1] = 1
w -= weights[i-1]
max_value = dp[n][capacity]
return max_value, items_selected
values = [8, 5, 12]
weights = [3, 2, 5]
capacity = 5
max_value, items_selected = knapsack(values, weights, capacity)
print(f"最大价值: {max_value}")
print(f"选中的物品: {items_selected}")
#最大价值: 13;选中的物品: [1, 1, 0]
练习3
A(0)地到E(9)地要铺设一条煤气管道,其中需经过三级中间站,两点之间的连线上的数字表示距离。如图所示,问应该选择什么路线,使总距离最短?
import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
# 定义网络图的邻接矩阵
inf = np.inf
graph = np.array([
[0, 2, 5, 1, inf, inf, inf, inf, inf, inf],
[inf, 0, inf, inf, 12, 14, 10, inf, inf, inf],
[inf, inf, 0, inf, 6, 10, 4, inf, inf, inf],
[inf, inf, inf, 0, 13, 12, 11, inf, inf, inf],
[inf, inf, inf, inf, 0, inf, inf, 3, 9, inf],
[inf, inf, inf, inf, inf, 0, inf, 6, 5, inf],
[inf, inf, inf, inf, inf, inf, 0, 18, 10, inf],
[inf, inf, inf, inf, inf, inf, inf, 0, inf, 5],
[inf, inf, inf, inf, inf, inf, inf, inf, 0, 2],
[inf, inf, inf, inf, inf, inf, inf, inf, inf, 0]
])
# 使用动态规划求解最短路径
def shortest_path_dp(graph, start, end):
n = len(graph)
dp = [inf] * n
dp[start] = 0
path = [-1] * n
for _ in range(n-1):
for u in range(n):
for v in range(n):
if graph[u][v] != inf and dp[u] + graph[u][v] < dp[v]:
dp[v] = dp[u] + graph[u][v]
path[v] = u
# 回溯路径
shortest_path = []
v = end
while v != start:
shortest_path.append(v)
v = path[v]
shortest_path.append(start)
shortest_path.reverse()
return dp[end], shortest_path
# 绘制网络图
def draw_graph(graph):
G = nx.DiGraph()
n = len(graph)
labels = {}
# 添加节点和边,并设置每个节点的层次
for i in range(n):
if i == 0:
subset = 0
elif 1 <= i <= 3:
subset = 1
elif 4 <= i <= 6:
subset = 2
elif 7 <= i <= 8:
subset = 3
elif i == 9:
subset = 4
G.add_node(i, subset=subset) # 设置层次结构
for j in range(n):
if graph[i][j] != inf and graph[i][j] != 0:
G.add_edge(i, j, weight=graph[i][j])
labels[(i, j)] = graph[i][j]
# 设定节点标签
pos = nx.multipartite_layout(G, subset_key="subset") # 设置层次结构
nx.draw(G, pos, with_labels=True, node_size=2000, node_color='lightgreen', font_size=10, font_weight='bold', arrowsize=20)
nx.draw_networkx_edge_labels(G, pos, edge_labels=labels, font_size=10)
plt.show()
start = 0 # 节点 A 对应索引 0
end = 9 # 节点 E 对应索引 9
min_distance, shortest_path = shortest_path_dp(graph, start, end)
print(f"最短距离: {min_distance}")
print(f"最短路径: {shortest_path}")
# 调用函数绘制图形
draw_graph(graph)
#最短距离: 19.0;最短路径: [0, 2, 4, 7, 9]
练习4
某人打算购买一辆新的货车用于运输,货车的售价是22 万元人民币。货车购买后,每年的各种保险费、养护费等费用如下表:
车龄(年) | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
费用(万元) | 3 | 5 | 10 | 16 | 21 |
另外,提到的5年内二手车销售价,假设表格的格式与上表相同,见下表,设计一种购买货车的方案,使5年内用车的总费用最少。
车龄(年) | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
费用(万元) | 17 | 15 | 7 | 4 | 2 |
# 数据初始化
purchase_price = 22 # 新车购买价格(万元)
annual_cost = [3, 5, 10, 16, 21] # 每年保险费和养护费(万元)
resale_value = [17, 15, 7, 4, 2] # 二手车销售价(万元)
years = len(annual_cost)
# 动态规划数组初始化
dp = [float('inf')] * (years + 1) # dp[i] 表示第i年末的最小总费用
buy_year = [0] * (years + 1) # buy_year[i] 表示第i年是否买新车(1表示买,0表示不买)
dp[years] = 0 # 第5年末的费用为0,因为不再有未来的费用
# 逆向动态规划求解
for i in range(years - 1, -1, -1):
min_cost = float('inf')
best_year = 0
for j in range(i + 1, years + 1):
cost = purchase_price + dp[j] # 第i年买新车的总成本
for k in range(i, j):
cost += annual_cost[k - i] # 加上每年的保险费和养护费
cost -= resale_value[j - i - 1] # 减去卖掉旧车的收益
if cost < min_cost:
min_cost = cost
best_year = j
dp[i] = min_cost
buy_year[i] = best_year
# 回溯找到购买新车的年份和费用细节
year = 0
purchase_plan = []
cost_details = []
while year < years:
purchase_year = buy_year[year]
if purchase_year != 0:
purchase_plan.append(year + 1)
annual_costs = sum(annual_cost[year:purchase_year])
resale_income = resale_value[purchase_year - year - 1]
cost_details.append((year + 1, purchase_price, annual_costs, resale_income))
year = purchase_year
# 构建每年的费用细节清单
annual_expenses = []
current_purchase_year = 0
for year in range(1, years + 1):
if year in purchase_plan:
current_purchase_year = year
annual_expenses.append({
"year": year,
"purchase_cost": purchase_price,
"maintenance_cost": annual_cost[year - current_purchase_year],
"resale_income": 0,
"buy_new": 1
})
else:
annual_expenses.append({
"year": year,
"purchase_cost": 0,
"maintenance_cost": annual_cost[year - current_purchase_year],
"resale_income": 0,
"buy_new": 0
})
if year == years or (year + 1 in purchase_plan):
annual_expenses[-1]["resale_income"] = resale_value[year - current_purchase_year]
# 输出最优用车计划和费用细节
print(f"5年内用车的最小总费用为:{dp[0]}万元")
print("最优的用车计划为:")
for year in purchase_plan:
print(f"在第 {year} 年购买新车")
# 输出费用清单
print("\n各年费用清单:")
for expense in annual_expenses:
print(f"第 {expense['year']} 年:")
print(f" 新车购买成本:{expense['purchase_cost']} 万元")
print(f" 保险费和养护费:{expense['maintenance_cost']} 万元")
print(f" 二手车收益:{expense['resale_income']} 万元")
print(f" 是否买新车:{'是' if expense['buy_new'] == 1 else '否'}")
5年内用车的最小总费用为:38万元
最优的用车计划为:
在第 1 年购买新车
在第 2 年购买新车
在第 4 年购买新车
各年费用清单:
第 1 年:
新车购买成本:22 万元
保险费和养护费:3 万元
二手车收益:17 万元
是否买新车:是
第 2 年:
新车购买成本:22 万元
保险费和养护费:3 万元
二手车收益:0 万元
是否买新车:是
第 3 年:
新车购买成本:0 万元
保险费和养护费:5 万元
二手车收益:15 万元
是否买新车:否
第 4 年:
新车购买成本:22 万元
保险费和养护费:3 万元
二手车收益:0 万元
是否买新车:是
第 5 年:
新车购买成本:0 万元
保险费和养护费:5 万元
二手车收益:15 万元
是否买新车:否
练习5
某种机器可在高低两种不同的负荷下生产。设机器在高负荷下生产的产量函数为 ,其中 为投入生产的机器数量。年完好率为;在低负荷下生产的产量函数为 ,其中 为投入生产的机器数量,年完好率为 。开始生产时完好的机器数量 台。问每年如何安排机器在高、低负荷下的生产,使在五年内生产的产品总产量最高。
解题过程
-
定义状态和变量
- 设
dp[t][u]
表示第t
年开始时有u
台机器用于高负荷生产的最大累计产量。 - 设
f[t][u]
记录安排策略,即在第t
年有u
台机器用于高负荷生产的情况下,低负荷生产的机器数量。
- 设
-
初始化
- 初始状态为
dp[0][0] = 0
,因为第一年开始时没有产量。 - 机器数量的范围为 0 到 1000。
- 初始状态为
-
状态转移
- 对于每年
t
,遍历所有可能的高负荷机器数量u
。 - 剩余的机器数量
v = s - u
用于低负荷生产。
- 对于每年
-
计算当年的产量:
- 高负荷产量:
- 低负荷产量:
-
计算第二年完好的机器数量:
- 高负荷完好率:
- 低负荷完好率:
- 第二年开始的完好机器数:
next_u = 0.7u
和next_v = 0.9v
更新dp
和记录策略f
。
-
回溯最佳路径
- 从第5年开始,按照记录的策略
f
回溯每年安排的机器数量。
- 从第5年开始,按照记录的策略
import numpy as np
# 参数初始化
s = 1000 # 初始完好机器数量
years = 5 # 总年数
a = 0.7 # 高负荷年完好率
b = 0.9 # 低负荷年完好率
# 动态规划数组初始化
dp = np.zeros((years + 1, s + 1)) # dp[t][u] 表示第t年开始时有u台机器用于高负荷生产的最大累计产量
f = np.zeros((years + 1, s + 1), dtype=int) # 记录安排策略
# 动态规划求解
for t in range(1, years + 1):
for u in range(s + 1):
v = s - u # 用于低负荷生产的机器数量
high_load_output = 8 * u ** 2
low_load_output = 5 * v
next_u = int(a * u)
next_v = int(b * v)
total_output = high_load_output + low_load_output
if next_u + next_v <= s:
if dp[t][u] < dp[t - 1][next_u + next_v] + total_output:
dp[t][u] = dp[t - 1][next_u + next_v] + total_output
f[t][u] = v
# 回溯找到最佳路径
u = 0
for i in range(s + 1):
if dp[years][i] > dp[years][u]:
u = i
plan = []
for t in range(years, 0, -1):
v = f[t][u]
plan.append((t, u, v))
u = int(a * u) + int(b * v)
plan.reverse()
# 输出结果
print(f"五年内生产的最高总产量为:{dp[years][0]}")
print("各年机器安排计划:")
for t, u, v in plan:
print(f"第 {t} 年:{u} 台机器用于高负荷生产,{v} 台机器用于低负荷生产")
五年内生产的最高总产量为:19672817.0
各年机器安排计划:
第 1 年:749 台机器用于高负荷生产,251 台机器用于低负荷生产
第 2 年:747 台机器用于高负荷生产,253 台机器用于低负荷生产
第 3 年:759 台机器用于高负荷生产,241 台机器用于低负荷生产
第 4 年:700 台机器用于高负荷生产,300 台机器用于低负荷生产
第 5 年:1000 台机器用于高负荷生产,0 台机器用于低负荷生产
练习6
某种工程设备的役龄为 4 年, 每年年初都面临着是否更新的问题: 若卖旧买新, 就要支付一定的购置费用; 若继续使用, 则要支付更多的维护费用, 且使用年限越长维护费用越多。役龄期内每年的年初购置价格, 当年维护费用及年末剩余净值如下表所示。为该设备制定一个 4 年役龄期内的更新计划, 使总的支付费用最少。
年份 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
年初购置价格 (万元) | 25 | 26 | 28 | 31 |
当年维护费用 (万元) | 10 | 14 | 18 | 26 |
年末剩余净值 (万元) | 20 | 16 | 13 | 11 |
可以把这个问题化为图论中的最短路问题。
构造赋权有向图,其中顶点集 ,这里 表示第 年年初的时刻,表示第 4 年年末的时刻;为边集;邻接矩阵,这里 为第 年年初至第年年初 (或年末) 期间所支付的费用,计算公式为
其中为第年年初的购置价格,为使用到第 年当年的维护费用,为使用年旧设备的出售价格,邻接矩阵
则制定总的支付费用最小的设备更新计划,就是求有向图从顶点到顶点的最短路。
import numpy as np
import networkx as nx
import pylab as plt
p=[25,26,28,31]; a=[10,14,18,26]; r=[20,16,13,11]
b=np.zeros((5,5)); #初始化邻接矩阵
for i in range(5):
for j in range(i+1,5):
b[i,j]=p[i]+np.sum(a[0:j-i])-r[j-i-1];
print(b)
G=nx.DiGraph(b)
p=nx.dijkstra_path(G, source=0, target=4, weight='weight') #求最短路径;
print("最短路径为:",np.array(p)+1) #python下标从0开始
d=nx.dijkstra_path_length(G, 0, 4, weight='weight') #求最短距离
print("所求的费用最小值为:",d)
s=dict(zip(range(5),range(1,6))) #构造用于顶点标注的标号字典
plt.rc('font',size=16)
pos=nx.shell_layout(G) #设置布局
w=nx.get_edge_attributes(G,'weight')
nx.draw(G,pos,font_weight='bold',labels=s,node_color='r')
nx.draw_networkx_edge_labels(G,pos,edge_labels=w)
path_edges=list(zip(p,p[1:]))
nx.draw_networkx_edges(G,pos,edgelist=path_edges,edge_color='r',width=3) #画出最短路径
#plt.savefig("figure10_9.png",pdi=500);
plt.show()
[[ 0. 15. 33. 54. 82.]
[ 0. 0. 16. 34. 55.]
[ 0. 0. 0. 18. 36.]
[ 0. 0. 0. 0. 21.]
[ 0. 0. 0. 0. 0.]]
最短路径为: [1 2 3 5]
所求的费用最小值为: 67.0
练习7
生产库存问题:工厂生产某种产品,每单位(千件)的成本为 1(千元),每次开工的固定成本为 3 (千元),工厂每季度的最大生产能力为 6(千件)。经调查,市场对该产品的需求量第一、二、三、四季度分别为 2,3,2,4(千件)。如果工厂在第一、二季度将全年的需求都生产出来,自然可以降低成本(少付固定成本费),但是对于第三、四季度才能上市的产品需付存储费,每季每千件的存储费为 0.5(千元)。还规定年初和年末这种产品均无库存。试制定一个生产计划,即安排每个季度的产量,使一年的总费用(生产成本和存储费)最少。
"""
这一类生产计划问题(Production planning problem),阶段按计划时间自然划分,状态定义为每阶段开始时的储存量x(k),决策为每个阶段的产量u(k),记每个阶段的需求量(已知量)为d(k),则状态转移方程为:
x(k+1) = x(k) + u(k) − d(k) ( x(k)≥0, k=1,2,...,n )
设每阶段开工的固定成本费为a,生产单位数量产品的成本费为b,每阶段单位数量产品的储存费为c,阶段指标为阶段的生产成本和储存费之和,即
if u(k)>0
v(x(k), u(k)) = cx(k) + (a + bu(k))
if u(k)=0
v(x(k), u(k)) = cx(k) + 0
最优值函数f(x(k))为从第k段的状态x(k)出发到过程终结的最小费用,满足 :
f(x(k)) = min(v(x(k), u(k)) + f(x(k+1)))
'''
# 每阶段单位数量产品的储存费为 C
C = 0.5
# 每阶段开工的固定成本费为 A
A = 3
# 生产单位数量产品的成本费为 B
B = 1
# 市场对该产品的需求量数组
demand = [2, 3, 2, 4]
# 计算市场对该产品的需求量
def demand_function(k):
return demand[k - 1]
# 生产目标,所有初始化为 None,在每次遍历的时候,需要更新这个值
production_target = [None] * len(demand)
def production(k):
return production_target[k - 1]
# 计算每阶段开始时的储存量
def storage(k):
if k <= 1:
return 0
else:
return storage(k - 1) + production(k - 1) - demand_function(k - 1)
# 计算阶段的生产成本和储存费之和
def cost(k):
if production(k) == 0:
return C * storage(k)
else:
return C * storage(k) + (A + B * production(k))
# 计算总成本
def total_cost(k):
if k == len(demand) + 1:
return 0
else:
return cost(k) + total_cost(k + 1)
# 生成所有可能的生产计划组合
possible_plans = []
for s1 in range(sum(demand) + 1):
for s2 in range(sum(demand) + 1):
for s3 in range(sum(demand) + 1):
for s4 in range(sum(demand) + 1):
if sum([s1, s2, s3, s4]) == sum(demand):
possible_plans.append([s1, s2, s3, s4])
# 筛选出符合条件的生产计划组合
for i in range(len(demand)):
possible_plans = [plan for plan in possible_plans if sum(plan[:i + 1]) >= sum(demand[:i + 1])]
# 计算每种生产计划组合的总成本
total_costs = []
for plan in possible_plans:
production_target = plan
total_costs.append(total_cost(1))
# 找到最小总成本和对应的最优生产计划
min_cost = min(total_costs)
min_index = total_costs.index(min_cost)
optimal_plan = possible_plans[min_index]
# 输出结果
print("最小总成本:", min_cost)
print("最优生产计划:", optimal_plan)
练习8
设某台新设备的年效益及年均维修费、更新净费用如下表所示。试确定今后5年内的更新策略,使总收益最大
万元\役龄 | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
效益 nk(t) | 5 | 4.5 | 4 | 3.75 | 3 | 2.5 |
维修费 uk(t) | 0.5 | 1 | 1.5 | 2 | 2.5 | 3 |
更新费 ck(t) | 0.5 | 1.5 | 2.2 | 2.5 | 3 | 3.5 |
from itertools import product
def calculate_total_profit(strategy):
# Given data
benefits = [5, 4.5, 4, 3.75, 3, 2.5] # rk(t)
maintenance_costs = [0.5, 1, 1.5, 2, 2.5, 3] # uk(t)
net_update_costs = [0.5, 1.5, 2.2, 2.5, 3, 3.5] # ck(t)
total_profit = 0
current_age = 0
yearly_net_income = []
for year, action in enumerate(strategy):
if action == 'K':
net_income = benefits[current_age] - maintenance_costs[current_age]
total_profit += net_income
yearly_net_income.append(net_income)
current_age += 1
elif action == 'R':
net_income = benefits[current_age] - maintenance_costs[current_age] - net_update_costs[year]
total_profit += net_income
yearly_net_income.append(net_income)
current_age = 0 # Reset age at the beginning of the next year
return total_profit, yearly_net_income
def find_optimal_strategy():
n = 5 # 5-year horizon
all_strategies = list(product(['K', 'R'], repeat=n))
max_profit = float('-inf')
optimal_strategy = None
optimal_yearly_net_income = None
for strategy in all_strategies:
# Skip invalid strategy where the first year is a replacement
if strategy[0] == 'R':
continue
total_profit, yearly_net_income = calculate_total_profit(strategy)
if total_profit > max_profit:
max_profit = total_profit
optimal_strategy = strategy
optimal_yearly_net_income = yearly_net_income
return max_profit, optimal_strategy, optimal_yearly_net_income
# Run the function to find the optimal strategy
max_profit, optimal_strategy, optimal_yearly_net_income = find_optimal_strategy()
# Print results
print(f"Maximum total profit over 5 years: {max_profit}万元")
print("Optimal decisions (R = Replace, K = Keep):", optimal_strategy)
print("Yearly Net Income:")
for year, income in enumerate(optimal_yearly_net_income, start=1):
print(f"Year {year}: {income}万元")
Maximum total profit over 5 years: 17.0万元
Optimal decisions (R = Replace, K = Keep): ('K', 'R', 'K', 'K', 'K')
Yearly Net Income:
Year 1: 4.5万元
Year 2: 2.0万元
Year 3: 4.5万元
Year 4: 3.5万元
Year 5: 2.5万元
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
2022-06-17 标准正态分布表—R语言