运筹学练习Python精解——动态规划

练习1

设国家拨给60万元投资,供四个工厂扩建使用,每个工厂扩建后的利润与投资额的大小有关,投资后的利润函数如下表所示,试给出收益最大的投资计划。

利润\投资 0 10 20 30 40 50 60
g1(r) 0 20 50 65 80 85 85
g2(x) 0 20 40 50 55 60 65
g3(x) 0 25 60 85 100 110 115
g4(x) 0 25 40 50 60 65 70

初始化:初始化一个动态规划表 f 和一个决策表 decisionf 用于存储最大利润,decision 用于记录每个工厂的最佳投资决策。
递归计算
- 对于每个工厂i从1到4。
- 对于每个可能的投资金额 j 从0到60。
- 对于每个可能分配给当前工厂i的投资金额 k(以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

求解下面背包问题的最优解。

maxz=8x1+5x2+12x3s.t.3x1+2x2+5x35x1,x2,x30

  

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

某种机器可在高低两种不同的负荷下生产。设机器在高负荷下生产的产量函数为 g=8u2,其中 u 为投入生产的机器数量。年完好率为a=0.7;在低负荷下生产的产量函数为 h=5y,其中 y 为投入生产的机器数量,年完好率为 b=0.9。开始生产时完好的机器数量 s=1000台。问每年如何安排机器在高、低负荷下的生产,使在五年内生产的产品总产量最高。

解题过程

  • 定义状态和变量

    • dp[t][u] 表示第 t 年开始时有 u 台机器用于高负荷生产的最大累计产量。
    • f[t][u] 记录安排策略,即在第 t 年有 u 台机器用于高负荷生产的情况下,低负荷生产的机器数量。
  • 初始化

    • 初始状态为 dp[0][0] = 0,因为第一年开始时没有产量。
    • 机器数量的范围为 0 到 1000。
  • 状态转移

    • 对于每年 t,遍历所有可能的高负荷机器数量 u
    • 剩余的机器数量 v = s - u 用于低负荷生产。
  • 计算当年的产量:

    • 高负荷产量:g=8u2
    • 低负荷产量:h=5v
  • 计算第二年完好的机器数量:

    • 高负荷完好率:a=0.7
    • 低负荷完好率:b=0.9
    • 第二年开始的完好机器数:next_u = 0.7unext_v = 0.9v
      更新 dp 和记录策略 f
  • 回溯最佳路径

    • 从第5年开始,按照记录的策略 f 回溯每年安排的机器数量。

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

可以把这个问题化为图论中的最短路问题。

构造赋权有向图D=(V,A,W),其中顶点集 V={v1,v2,,v5},这里 vi(i=1,2,3,4)表示第 i 年年初的时刻,v5表示第 4 年年末的时刻;A为边集;邻接矩阵W=(wij)(5×5),这里wij​ 为第 i年年初至第j年年初 (或j1年末) 期间所支付的费用,计算公式为

wij=pi+k=1jiakrji

其中pi为第i年年初的购置价格,ak为使用到第 k年当年的维护费用,ri为使用i年旧设备的出售价格,邻接矩阵

W=[0153354820163455018360210]

则制定总的支付费用最小的设备更新计划,就是求有向图D从顶点v1到顶点v5的最短路。

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万元
posted @   郝hai  阅读(67)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
历史上的今天:
2022-06-17 标准正态分布表—R语言
点击右上角即可分享
微信分享提示