运筹学练习Python精解——网络计划技术
练习1
某新产品研制项目的各项工序、所需时间及相互关系如下表所示,试画出该项目的网络图,并求出关键路线。
工序 | 产品及工艺设计 | 外购配套件 | 下料、锻件 | 工装制造1 | 木模、铸件 | 机械加工1 | 工装制造2 | 机械加工2 | 机械加工3 | 装配调试 |
---|---|---|---|---|---|---|---|---|---|---|
工序代号 | A | B | C | D | E | F | G | H | I | K |
所需时间 | 60 | 45 | 10 | 20 | 40 | 18 | 30 | 15 | 25 | 35 |
紧后工序 | B, C, D, E | K | F | G, H | H | K | I | K | K | - |
1.1 绘制网络计划图(数学模型)
1.2 Python求解
#网络计划的最长路算法
import networkx as nx
# Define the edges with the new data structure
edges = {
'A': {'nodes': ('1', '2'), 'weight': 60},
'B': {'nodes': ('2', '7'), 'weight': 45},
'C': {'nodes': ('2', '3'), 'weight': 10},
'D': {'nodes': ('2', '4'), 'weight': 20},
'E': {'nodes': ('2', '5'), 'weight': 40},
'F': {'nodes': ('3', '7'), 'weight': 18},
'G': {'nodes': ('4', '6'), 'weight': 30},
'L': {'nodes': ('4', '5'), 'weight': 0},
'H': {'nodes': ('5', '7'), 'weight': 15},
'I': {'nodes': ('6', '7'), 'weight': 25},
'K': {'nodes': ('7', '8'), 'weight': 35}
}
# Initialize a directed graph
G = nx.DiGraph()
# Add edges to the graph using the new data structure
for edge_name, edge_data in edges.items():
start, end = edge_data['nodes']
weight = edge_data['weight']
G.add_edge(start, end, weight=weight, name=edge_name)
# Compute the longest path using the networkx library
length, path = nx.algorithms.dag.dag_longest_path_length(G, weight='weight'), nx.algorithms.dag.dag_longest_path(G, weight='weight')
# Extract the names of the edges in the critical path
critical_path_edges = []
for i in range(len(path) - 1):
critical_path_edges.append(G[path[i]][path[i + 1]]['name'])
# Format the critical path output
formatted_critical_path = " -> ".join(critical_path_edges) + "."
print(f"Length of the critical path: {length}")
print(f"Critical path nodes: {path}")
print(f"Critical path edges: {formatted_critical_path}")
Length of the critical path: 170
Critical path nodes: ['1', '2', '4', '6', '7', '8']
Critical path edges: A -> D -> G -> I -> K.
练习2
import networkx as nx
# Define the edges with the new data structure
edges = {
'A': {'nodes': ('1', '2'), 'weight': 4},
'B': {'nodes': ('2', '3'), 'weight': 5},
'C': {'nodes': ('2', '4'), 'weight': 3},
'D': {'nodes': ('2', '6'), 'weight': 2},
'E': {'nodes': ('3', '4'), 'weight': 2},
'F': {'nodes': ('3', '5'), 'weight': 1},
'G': {'nodes': ('4', '6'), 'weight': 4},
'H': {'nodes': ('5', '8'), 'weight': 3},
'I': {'nodes': ('6', '7'), 'weight': 2},
'J': {'nodes': ('7', '8'), 'weight': 4}
}
# Initialize a directed graph
G = nx.DiGraph()
# Add edges to the graph using the new data structure
for edge_name, edge_data in edges.items():
start, end = edge_data['nodes']
weight = edge_data['weight']
G.add_edge(start, end, weight=weight, name=edge_name)
# Compute the longest path using the networkx library
length, path = nx.algorithms.dag.dag_longest_path_length(G, weight='weight'), nx.algorithms.dag.dag_longest_path(G, weight='weight')
# Extract the names of the edges in the critical path
critical_path_edges = []
for i in range(len(path) - 1):
critical_path_edges.append(G[path[i]][path[i + 1]]['name'])
# Format the critical path output
formatted_critical_path = " -> ".join(critical_path_edges) + "."
print(f"Length of the critical path: {length}")
print(f"Critical path nodes: {path}")
print(f"Critical path edges: {formatted_critical_path}")
Length of the critical path: 21
Critical path nodes: ['1', '2', '3', '4', '6', '7', '8']
Critical path edges: A -> B -> E -> G -> I -> J.
练习3
某项目的作业明细表如下,试计算其工序的作业时间参数,最后作甘特图表示关键路线。
工作 | A | B | C | D | E | F | G | H | I | J |
---|---|---|---|---|---|---|---|---|---|---|
持续时间 | 2 | 3 | 5 | 2 | 3 | 3 | 2 | 3 | 6 | 2 |
紧前工作 | - | A | A | B | B | D | F | E,F | C,E,F | G,H |
3.1 网络计划图
3.2 Python程序
- 作业明细表数据输入结构
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.patches import Patch
# 第一部分:数据结构
# 定义任务字典('partment': 'IT'仅仅为了后面作图采用不同的颜色表示,无实际意义)
dictA = {'工作名称': 'A', '紧前工作': '-', '持续时间': 2, 'partment': 'IT'}
dictB = {'工作名称': 'B', '紧前工作': 'A', '持续时间': 3, 'partment': 'MKT'}
dictC = {'工作名称': 'C', '紧前工作': 'A', '持续时间': 5, 'partment': 'ENG'}
dictD = {'工作名称': 'D', '紧前工作': 'B', '持续时间': 2, 'partment': 'PROD'}
dictE = {'工作名称': 'E', '紧前工作': 'B', '持续时间': 3, 'partment': 'FIN'}
dictF = {'工作名称': 'F', '紧前工作': 'D', '持续时间': 3, 'partment': 'IT'}
dictG = {'工作名称': 'G', '紧前工作': 'F', '持续时间': 2, 'partment': 'MKT'}
dictH = {'工作名称': 'H', '紧前工作': 'E,F', '持续时间': 3, 'partment': 'PROD'}
dictI = {'工作名称': 'I', '紧前工作': 'C,E,F', '持续时间': 6, 'partment': 'FIN'}
dictJ = {'工作名称': 'J', '紧前工作': 'G,H', '持续时间': 2, 'partment': 'MKT'}
lis = [dictA, dictB, dictC, dictD, dictE, dictF, dictG, dictH, dictI,dictJ]
# 创建 DataFrame
df = pd.DataFrame(lis)
- 工序时间参数的计算
# 初始化 CPM 分析的列
df['Earliest_Start'] = 0
df['Earliest_Finish'] = 0
df['Latest_Start'] = 0
df['Latest_Finish'] = 0
df['Total_Float'] = 0
df['Free_Float'] = 0
# 第二部分:网络参数计算
# 正向传递:计算最早开始和最早结束时间
for i, row in df.iterrows():
if row['紧前工作'] == '-':
df.at[i, 'Earliest_Finish'] = row['持续时间']
else:
predecessors = row['紧前工作'].split(',')
max_ef = 0
for pred in predecessors:
pred_ef = df.loc[df['工作名称'] == pred, 'Earliest_Finish'].values[0]
if pred_ef > max_ef:
max_ef = pred_ef
df.at[i, 'Earliest_Start'] = max_ef
df.at[i, 'Earliest_Finish'] = max_ef + row['持续时间']
# 反向传递:计算最迟开始和最迟结束时间
project_duration = df['Earliest_Finish'].max()
for i in range(len(df) - 1, -1, -1):
row = df.iloc[i]
if i == len(df) - 1:
df.at[i, 'Latest_Finish'] = project_duration
df.at[i, 'Latest_Start'] = project_duration - row['持续时间']
else:
successors = df[df['紧前工作'].str.contains(row['工作名称'])]
min_ls = project_duration
for j, succ_row in successors.iterrows():
succ_ls = df.at[j, 'Latest_Start']
if succ_ls < min_ls:
min_ls = succ_ls
df.at[i, 'Latest_Finish'] = min_ls
df.at[i, 'Latest_Start'] = min_ls - row['持续时间']
# 计算总浮动和自由浮动
for i, row in df.iterrows():
df.at[i, 'Total_Float'] = row['Latest_Start'] - row['Earliest_Start']
successors = df[df['紧前工作'].str.contains(row['工作名称'])]
if len(successors) == 0:
df.at[i, 'Free_Float'] = df.at[i, 'Total_Float']
else:
min_es = min(successors['Earliest_Start']) if not successors.empty else row['Earliest_Finish']
df.at[i, 'Free_Float'] = min_es - row['Earliest_Finish']
# 将DataFrame转置并完整显示
pd.set_option('display.max_columns', None)
print(df.T)
工作名称 A B C D E F G H I J
紧前工作 - A A B B D F E,F C,E,F G,H
持续时间 2 3 5 2 3 3 2 3 6 2
partment IT MKT ENG PROD FIN IT MKT PROD FIN MKT
Earliest_Start 0 2 2 5 5 7 10 10 10 13
Earliest_Finish 2 5 7 7 8 10 12 13 16 15
Latest_Start 0 2 5 5 7 7 12 11 10 14
Latest_Finish 2 5 10 7 10 10 14 14 16 16
Total_Float 0 0 3 0 2 0 2 1 0 1
Free_Float 0 0 3 0 2 0 1 0 0 1
- 可视化甘特图
# 反转 DataFrame 的顺序用于绘图
df = df.iloc[::-1]
# 绘制甘特图
fig, ax = plt.subplots(1, figsize=(16, 6))
# 绘制任务条
ax.barh(df['工作名称'], df['current_num'], left=df['Earliest_Start'], color=df['color'])
ax.barh(df['工作名称'], df['days_start_to_end'], left=df['Earliest_Start'], color=df['color'], alpha=0.5)
# 添加百分比文本
for idx, row in df.iterrows():
ax.text(row['Latest_Finish'] + 0.1, len(df) - idx - 1, f"{int(row['Flexible'] * 100)}%", va='center', alpha=0.8)
# 部门图例
c_dict = {'MKT': '#E64646', 'FIN': '#E69646', 'ENG': '#34D05C', 'PROD': '#34D0C3', 'IT': '#3475D0'}
legend_elements = [Patch(facecolor=c_dict[i], label=i) for i in c_dict]
plt.legend(handles=legend_elements)
plt.xlabel('Days')
plt.ylabel('Tasks')
plt.title('Gantt Chart')
plt.show()
!!!工序的百分比表示该工序机动时间占其完工时间的百分比,关键工序的百分比为0%。
练习4
某公司的产前分析网络工程图的工序明细表和网络图如下图所示,求出网络中每个节点的最早开始时间、最迟开始时间、关键路线及工程工期。
工作 | A | B | C | D | E | F | G | H | I | J | K |
---|---|---|---|---|---|---|---|---|---|---|---|
持续时间 | 5 | 10 | 11 | 4 | 4 | 15 | 21 | 35 | 25 | 15 | 20 |
紧前工作 | - | - | - | B | A | C,D | B,E | B,E | B,E | F,G,I | F,G |
4.1 网络计划图
4.2 Python程序
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.patches import Patch
# 第一部分:数据结构
# 定义任务字典('partment': 'IT'仅仅为了后面作图采用不同的颜色表示,无实际意义)
dictA = {'工作名称': 'A', '紧前工作': '-', '持续时间': 5, 'partment': 'IT'}
dictB = {'工作名称': 'B', '紧前工作': '-', '持续时间': 10, 'partment': 'MKT'}
dictC = {'工作名称': 'C', '紧前工作': '-', '持续时间': 11, 'partment': 'ENG'}
dictD = {'工作名称': 'D', '紧前工作': 'B', '持续时间': 4, 'partment': 'PROD'}
dictE = {'工作名称': 'E', '紧前工作': 'A', '持续时间': 4, 'partment': 'FIN'}
dictF = {'工作名称': 'F', '紧前工作': 'C,D', '持续时间': 15, 'partment': 'IT'}
dictG = {'工作名称': 'G', '紧前工作': 'B,E', '持续时间': 21, 'partment': 'MKT'}
dictH = {'工作名称': 'H', '紧前工作': 'B,E', '持续时间': 35, 'partment': 'PROD'}
dictI = {'工作名称': 'I', '紧前工作': 'B,E', '持续时间': 25, 'partment': 'FIN'}
dictJ = {'工作名称': 'J', '紧前工作': 'F,G,I', '持续时间': 15, 'partment': 'MKT'}
dictK = {'工作名称': 'K', '紧前工作': 'F,G', '持续时间': 20, 'partment': 'FIN'}
lis = [dictA, dictB, dictC, dictD, dictE, dictF, dictG, dictH, dictI,dictJ,dictK]
# 创建 DataFrame
df = pd.DataFrame(lis)
#后面语句与练习3完全相同
练习5
已知网络计划各工作的正常完工、极限完工及相应费用如下表所示,试分析其最小成本日程。
工序 | 紧前工序 | 作业正常完工(天) | 作业极限完工(天) | 工序正常完工(直接费用/元) | 工序极限完工(直接费用/元) | 费率 |
---|---|---|---|---|---|---|
A | - | 3 | 3 | 800 | 800 | - |
B | - | 5 | 3 | 1600 | 1900 | 150 |
C | A | 7 | 3 | 2000 | 2800 | 200 |
D | B | 6 | 3 | 2000 | 2300 | 100 |
E | B | 5 | 2 | 500 | 860 | 120 |
F | E | 3 | 3 | 1000 | 1000 | - |
G | D | 4 | 3 | 900 | 1100 | 200 |
合计 | - | - | - | 8800 | - | - |
间接费用 | - | - | - | 200 元/天 | - | - |
5.1 网络计划图
5.2 工序的时间参数
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.patches import Patch
# 第一部分:数据结构
# 定义任务字典('partment': 'IT'仅仅为了后面作图采用不同的颜色表示,无实际意义)
dictA = {'工作名称': 'A', '紧前工作': '-', '持续时间': 3, 'partment': 'IT'}
dictB = {'工作名称': 'B', '紧前工作': '-', '持续时间': 5, 'partment': 'MKT'}
dictC = {'工作名称': 'C', '紧前工作': 'A', '持续时间': 7, 'partment': 'ENG'}
dictD = {'工作名称': 'D', '紧前工作': 'B', '持续时间': 6, 'partment': 'PROD'}
dictE = {'工作名称': 'E', '紧前工作': 'B', '持续时间': 5, 'partment': 'FIN'}
dictF = {'工作名称': 'F', '紧前工作': 'E', '持续时间': 3, 'partment': 'IT'}
dictG = {'工作名称': 'G', '紧前工作': 'D', '持续时间': 4, 'partment': 'MKT'}
lis = [dictA, dictB, dictC, dictD, dictE, dictF, dictG]
# 创建 DataFrame
df = pd.DataFrame(lis)
# 初始化 CPM 分析的列
df['Earliest_Start'] = 0
df['Earliest_Finish'] = 0
df['Latest_Start'] = 0
df['Latest_Finish'] = 0
df['Total_Float'] = 0
df['Free_Float'] = 0
# 第二部分:网络参数计算
# 正向传递:计算最早开始和最早结束时间
for i, row in df.iterrows():
if row['紧前工作'] == '-':
df.at[i, 'Earliest_Finish'] = row['持续时间']
else:
predecessors = row['紧前工作'].split(',')
max_ef = 0
for pred in predecessors:
pred_ef = df.loc[df['工作名称'] == pred, 'Earliest_Finish'].values[0]
if pred_ef > max_ef:
max_ef = pred_ef
df.at[i, 'Earliest_Start'] = max_ef
df.at[i, 'Earliest_Finish'] = max_ef + row['持续时间']
# 反向传递:计算最迟开始和最迟结束时间
project_duration = df['Earliest_Finish'].max()
for i in range(len(df) - 1, -1, -1):
row = df.iloc[i]
if i == len(df) - 1:
df.at[i, 'Latest_Finish'] = project_duration
df.at[i, 'Latest_Start'] = project_duration - row['持续时间']
else:
successors = df[df['紧前工作'].str.contains(row['工作名称'])]
min_ls = project_duration
for j, succ_row in successors.iterrows():
succ_ls = df.at[j, 'Latest_Start']
if succ_ls < min_ls:
min_ls = succ_ls
df.at[i, 'Latest_Finish'] = min_ls
df.at[i, 'Latest_Start'] = min_ls - row['持续时间']
# 计算总浮动和自由浮动
for i, row in df.iterrows():
df.at[i, 'Total_Float'] = row['Latest_Start'] - row['Earliest_Start']
successors = df[df['紧前工作'].str.contains(row['工作名称'])]
if len(successors) == 0:
df.at[i, 'Free_Float'] = df.at[i, 'Total_Float']
else:
min_es = min(successors['Earliest_Start']) if not successors.empty else row['Earliest_Finish']
df.at[i, 'Free_Float'] = min_es - row['Earliest_Finish']
# 将DataFrame转置并完整显示
pd.set_option('display.max_columns', None)
print(df.T)
工作名称 A B C D E F G
紧前工作 - - A B B E D
持续时间 3 5 7 6 5 3 4
Earliest_Start 0 0 3 5 5 10 11
Earliest_Finish 3 5 10 11 10 13 15
Latest_Start 5 0 8 5 7 12 11
Latest_Finish 8 5 15 11 12 15 15
Total_Float 5 0 5 0 2 2 0
Free_Float 0 0 5 0 0 2 0
5.3 最小成本日程
要找到最小成本日程,我们需要计算在各种情况下的直接费用和间接费用的总和,选择总成本最小的方案。首先我们需要列出每个工序的可能工期,计算其直接费用,然后再加上间接费用。按正常工时从下图中计算出总工期为15天。关键路线为①→③→④→⑥。
要寻找最小成本日程,计算按下列步骤进行:
(1)从关键工作中选出缩短工时所需直接费用最少的方案,并确定该方案可能缩短的天数;
(2)按照工作的新工时,重新计算网络计划的关键路线及关键工作;
(3)计算由于缩短工时所增加的直接费用。
不断重复上述三个步骤,直到工期不能再缩短为止。
从网络图看出,关键路线上的三道关键工作(1,3)、(3,4)、(4,6)中,工作(3,4)的成本斜率相比之下最小,应选择在工作(3,4)上缩短工时,查表可知,最多可缩短3天,即取工作(3,4)新工时为6-3=3(天)。重新计算网络图时间参数,结果如图a所示,关键路线为①→③→⑤→⑥,工期为13天,实际只缩短了2天。这意味着(3,4)工作没有必要减少3天,(3,4)工时应取6-2=4(天)。重新计算,结果如图b,总工期为13天,有两条关键路线:①→③→④→⑥与①→③→⑤→⑥,此次调整增加直接费用2×100=200(元)。
图a | 图b |
---|---|
![]() |
![]() |
重复步骤(1)、(2)、(3),必须注意两条关键路线应同时缩短。有如下几个方案可选择:
①在B工作缩短1天,需要费用150(元);
②在D、E上同时缩短1天,需费用100+120=220(元);
③在G、E上同时缩短1天,需费用200+120=320(元);
取费用最小方案①,最多缩短2天,工时变为5-2=3(天)。重新计算网络时间参数,结果如图c所示。总工期为11天,这时的关键路径仍为两条:①→③→④→⑥与①→③→⑤→⑥,增加直接费用2×150=300(元)。
图c | 图d |
---|---|
![]() |
![]() |
再进行第三次调整,可供选择的方案如下:
①在D、E上同时缩短1天,需费用100+120=220(元);
②在G、E上同时缩短1天,需费用200+120=320(元);
因为间接费用每减少1天缩短200元,而上述方案每缩短一天增加的费用均大于间接费用,所以继续缩短工期并不能减少费用,则停止计算,计算结果如图d所示。
过程名称 | 工作名称 | 可缩短天数/d | 实际缩短天数/d | 总直接费用/元 | 总间接费用/元 | 总成本/元 | 总工期/d |
---|---|---|---|---|---|---|---|
0 | - | - | - | 8800 | 3000 | 11800 | 15 |
1 | D | 3 | 2 | 9000 | 2600 | 11600 | 13 |
2 | B | 2 | 2 | 9300 | 2200 | 11500 | 11 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!