长安的荔枝最优转运路线:用 Python 实现荔枝运输结合时间与费用的多目标最短路径算法
系统已整合为一个python文件,可在文末直接复制运行使用。
背景介绍
荔枝是岭南的特产,古时有“荔枝一日千里”的说法,将荔枝从岭南(深圳)运往长安(西安)需要在时间与运输费用间取得平衡。
本文围绕“荔枝运输路径优化”这一实际问题,利用经典图论算法(Dijkstra 和 A*),结合运输时间与费用的多目标优化,为运输规划提供最优路径选择方案。同时实现断路模拟,模拟实际运输过程中的突发事件。
长安的荔枝最优转运路线系统效果展示
设置权重后选择Dijkstra算法:
设置权重后选择A* 算法算法:
断路:武汉—西安
再断路郑州到长沙
任务目标
- 基于给定城市图(节点为城市,边带有运输时间和费用)
- 实现多目标路径优化,可根据用户偏好调整时间与费用权重
- 支持断路逻辑,模拟城市间运输线路中断
- 提供可视化路径和统计图表,方便理解运输方案
1. 问题建模
给定如下图,节点代表城市,边权包含运输时间(小时)和费用(元):
city_graph = {
'深圳': {'广州': {'time': 1.5, 'cost': 100}, '东莞': {'time': 1.0, 'cost': 80}},
'广州': {'深圳': {'time': 1.5, 'cost': 100}, '韶关': {'time': 2.5, 'cost': 150}, '长沙': {'time': 5.5, 'cost': 300}},
'东莞': {'深圳': {'time': 1.0, 'cost': 80}, '惠州': {'time': 1.2, 'cost': 90}},
'惠州': {'东莞': {'time': 1.2, 'cost': 90}, '武汉': {'time': 8.0, 'cost': 400}},
'韶关': {'广州': {'time': 2.5, 'cost': 150}, '长沙': {'time': 4.0, 'cost': 250}},
'长沙': {'韶关': {'time': 4.0, 'cost': 250}, '武汉': {'time': 3.0, 'cost': 180}, '郑州': {'time': 8.0, 'cost': 350}},
'武汉': {'惠州': {'time': 8.0, 'cost': 400}, '长沙': {'time': 3.0, 'cost': 180}, '郑州': {'time': 4.5, 'cost': 220}, '西安': {'time': 10.0, 'cost': 500}},
'郑州': {'长沙': {'time': 8.0, 'cost': 350}, '武汉': {'time': 4.5, 'cost': 220}, '洛阳': {'time': 2.0, 'cost': 120}},
'洛阳': {'郑州': {'time': 2.0, 'cost': 120}, '西安': {'time': 5.0, 'cost': 280}},
'西安': {'武汉': {'time': 10.0, 'cost': 500}, '洛阳': {'time': 5.0, 'cost': 280}}
}
2. 多目标成本函数设计
运输时间与费用往往存在权衡,我们设计一个线性加权的综合成本函数:
def combined_cost(time, cost, time_weight, cost_weight):
return time_weight * time + cost_weight * cost
用户可通过界面调节时间权重和费用权重,灵活调整最优路径计算标准。
3. 路径搜索算法实现
Dijkstra 多目标版
利用优先队列维护当前节点的最小代价,扩展到多目标加权成本:
def dijkstra_multiobj(graph, start, goal, time_weight=1.0, cost_weight=0.0):
heap = [(0, start, [start])]
visited = set()
while heap:
cost_so_far, node, path = heapq.heappop(heap)
if node == goal:
return path, cost_so_far
if node in visited:
continue
visited.add(node)
for neighbor, info in graph.get(node, {}).items():
if neighbor not in visited:
cost_edge = combined_cost(info['time'], info['cost'], time_weight, cost_weight)
heapq.heappush(heap, (cost_so_far + cost_edge, neighbor, path + [neighbor]))
return None, float('inf')
A* 多目标版
引入启发函数(估计从当前城市到终点的最短距离),提升搜索效率:
heuristic = {'深圳': 10, '广州': 8, '东莞': 9, '惠州': 7, '韶关': 6, '长沙': 4, '武汉': 3, '郑州': 2, '洛阳': 1, '西安': 0}
def a_star_multiobj(graph, start, goal, heuristic, time_weight=1.0, cost_weight=0.0):
open_list = [(0 + heuristic.get(start, 0), 0, start, [])]
closed_list = set()
g_costs = {start: 0}
while open_list:
_, g_cost, current, path = heapq.heappop(open_list)
if current in closed_list:
continue
closed_list.add(current)
path = path + [current]
if current == goal:
return path, g_cost
for neighbor, info in graph.get(current, {}).items():
if neighbor in closed_list:
continue
cost_edge = combined_cost(info['time'], info['cost'], time_weight, cost_weight)
tentative_g_cost = g_cost + cost_edge
if neighbor not in g_costs or tentative_g_cost < g_costs[neighbor]:
g_costs[neighbor] = tentative_g_cost
f_cost = tentative_g_cost + heuristic.get(neighbor, 0)
heapq.heappush(open_list, (f_cost, tentative_g_cost, neighbor, path))
return None, float('inf')
4. 断路模拟与恢复机制
现实运输中可能出现路段中断,我们支持动态断路功能:
def remove_edge(graph, city1, city2):
if city2 in graph.get(city1, {}):
del graph[city1][city2]
if city1 in graph.get(city2, {}):
del graph[city2][city1]
def restore_all_edges():
st.session_state.graph = copy.deepcopy(city_graph)
st.session_state.broken_edges.clear()
界面允许用户选定断路边,动态更新图结构,实时影响路径搜索结果。
5. 可视化展示
基于 NetworkX 和 Matplotlib,实现图结构可视化,路径以红色高亮,同时显示每条边的时间与费用:
def draw_graph_enhanced(graph, path=None, time_weight=1.0, cost_weight=0.0):
G = nx.Graph()
for u in graph:
for v, info in graph[u].items():
G.add_edge(u, v, time=info['time'], cost=info['cost'])
pos = nx.spring_layout(G, seed=42)
plt.figure(figsize=(10,6))
nx.draw(G, pos, with_labels=True, node_color='lightblue', node_size=1000, font_size=12)
edge_labels = {(u,v): f"{d['time']}h/{d['cost']}元" for u,v,d in G.edges(data=True)}
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels)
if path:
path_edges = list(zip(path, path[1:]))
nx.draw_networkx_edges(G, pos, edgelist=path_edges, edge_color='red', width=3)
edge_label_path = {}
for u,v in path_edges:
info = city_graph[u][v]
combined = combined_cost(info['time'], info['cost'], time_weight, cost_weight)
edge_label_path[(u,v)] = f"{combined:.1f}"
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_label_path, font_color='red', font_size=10)
plt.title("城市运输图及最优路径(红色)")
st.pyplot(plt)
此外,通过柱状图展示路径各段的时间和费用分布,帮助用户做出权衡。
6. 用户交互体验
基于 Streamlit,搭建交互界面:
- 选择起点、终点城市
- 调节时间和费用权重滑块,实时改变搜索目标
- 添加或恢复断路,模拟实际运输突发情况
- 选择算法(Dijkstra 或 A*)
- 点击计算按钮,显示最优路径、分段时间费用、总时间总费用,并渲染路径图与柱状图
界面简洁易用,适合物流规划人员快速决策。
完整代码
import heapq
import networkx as nx
import matplotlib.pyplot as plt
import streamlit as st
import copy
import numpy as np
plt.rcParams['font.sans-serif'] = ['SimHei'] # 中文字体
plt.rcParams['axes.unicode_minus'] = False # 负号正常显示
city_graph = {
'深圳': {'广州': {'time': 1.5, 'cost': 100}, '东莞': {'time': 1.0, 'cost': 80}},
'广州': {'深圳': {'time': 1.5, 'cost': 100}, '韶关': {'time': 2.5, 'cost': 150}, '长沙': {'time': 5.5, 'cost': 300}},
'东莞': {'深圳': {'time': 1.0, 'cost': 80}, '惠州': {'time': 1.2, 'cost': 90}},
'惠州': {'东莞': {'time': 1.2, 'cost': 90}, '武汉': {'time': 8.0, 'cost': 400}},
'韶关': {'广州': {'time': 2.5, 'cost': 150}, '长沙': {'time': 4.0, 'cost': 250}},
'长沙': {'韶关': {'time': 4.0, 'cost': 250}, '武汉': {'time': 3.0, 'cost': 180}, '郑州': {'time': 8.0, 'cost': 350}},
'武汉': {'惠州': {'time': 8.0, 'cost': 400}, '长沙': {'time': 3.0, 'cost': 180}, '郑州': {'time': 4.5, 'cost': 220}, '西安': {'time': 10.0, 'cost': 500}},
'郑州': {'长沙': {'time': 8.0, 'cost': 350}, '武汉': {'time': 4.5, 'cost': 220}, '洛阳': {'time': 2.0, 'cost': 120}},
'洛阳': {'郑州': {'time': 2.0, 'cost': 120}, '西安': {'time': 5.0, 'cost': 280}},
'西安': {'武汉': {'time': 10.0, 'cost': 500}, '洛阳': {'time': 5.0, 'cost': 280}}
}
heuristic = {
'深圳': 10, '广州': 8, '东莞': 9, '惠州': 7, '韶关': 6,
'长沙': 4, '武汉': 3, '郑州': 2, '洛阳': 1, '西安': 0
}
def combined_cost(time, cost, time_weight, cost_weight):
return time_weight * time + cost_weight * cost
def remove_edge(graph, city1, city2):
if city2 in graph.get(city1, {}):
del graph[city1][city2]
if city1 in graph.get(city2, {}):
del graph[city2][city1]
def dijkstra_multiobj(graph, start, goal, time_weight=1.0, cost_weight=0.0):
heap = [(0, start, [start])]
visited = set()
while heap:
cost_so_far, node, path = heapq.heappop(heap)
if node == goal:
return path, cost_so_far
if node in visited:
continue
visited.add(node)
for neighbor, info in graph.get(node, {}).items():
if neighbor not in visited:
cost_edge = combined_cost(info['time'], info['cost'], time_weight, cost_weight)
heapq.heappush(heap, (cost_so_far + cost_edge, neighbor, path + [neighbor]))
return None, float('inf')
def a_star_multiobj(graph, start, goal, heuristic, time_weight=1.0, cost_weight=0.0):
open_list = [(0 + heuristic.get(start,0), 0, start, [])]
closed_list = set()
g_costs = {start: 0}
while open_list:
_, g_cost, current, path = heapq.heappop(open_list)
if current in closed_list:
continue
closed_list.add(current)
path = path + [current]
if current == goal:
return path, g_cost
for neighbor, info in graph.get(current, {}).items():
if neighbor in closed_list:
continue
cost_edge = combined_cost(info['time'], info['cost'], time_weight, cost_weight)
tentative_g_cost = g_cost + cost_edge
if neighbor not in g_costs or tentative_g_cost < g_costs[neighbor]:
g_costs[neighbor] = tentative_g_cost
f_cost = tentative_g_cost + heuristic.get(neighbor, 0)
heapq.heappush(open_list, (f_cost, tentative_g_cost, neighbor, path))
return None, float('inf')
def draw_graph_enhanced(graph, path=None, time_weight=1.0, cost_weight=0.0):
G = nx.Graph()
for u in graph:
for v, info in graph[u].items():
G.add_edge(u, v, time=info['time'], cost=info['cost'])
pos = nx.spring_layout(G, seed=42)
plt.figure(figsize=(10,6))
nx.draw(G, pos, with_labels=True, node_color='lightblue', node_size=1000, font_size=12)
edge_labels = {(u,v): f"{d['time']}h/{d['cost']}元" for u,v,d in G.edges(data=True)}
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels)
if path:
path_edges = list(zip(path, path[1:]))
nx.draw_networkx_edges(G, pos, edgelist=path_edges, edge_color='red', width=3)
edge_label_path = {}
for u,v in path_edges:
info = city_graph[u][v]
combined = combined_cost(info['time'], info['cost'], time_weight, cost_weight)
edge_label_path[(u,v)] = f"{combined:.1f}"
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_label_path, font_color='red', font_size=10)
plt.title("城市运输图及最优路径(红色)")
st.pyplot(plt)
def restore_all_edges():
st.session_state.graph = copy.deepcopy(city_graph)
st.session_state.broken_edges.clear()
def main():
st.title("荔枝运输路线优化:岭南 → 长安(时间&费用双目标)")
# 居中显示图片
st.markdown(
"""
<div style="text-align: center;">
<img src="https://i.ytimg.com/vi/JoXynWKVD0Q/maxresdefault.jpg" width="60%" />
<p style="font-size: 16px; color: gray;">长安的荔枝</p>
</div>
""",
unsafe_allow_html=True
)
cities = list(city_graph.keys())
if 'graph' not in st.session_state:
st.session_state.graph = copy.deepcopy(city_graph)
if 'broken_edges' not in st.session_state:
st.session_state.broken_edges = set()
start = st.selectbox("请选择起点", cities, index=cities.index('深圳'))
end = st.selectbox("请选择终点", cities, index=cities.index('西安'))
time_weight = st.slider("运输时间权重", 0.0, 1.0, 0.7, 0.05)
cost_weight = st.slider("运输费用权重", 0.0, 1.0, 0.3, 0.05)
if abs(time_weight + cost_weight - 1.0) > 0.01:
st.warning("建议时间权重和费用权重之和为1,可以调整滑块使之接近1。")
st.markdown("### 模拟断路(可多条断路)")
col1, col2 = st.columns(2)
with col1:
city_a = st.selectbox("断路城市A", cities, key='break_city_a')
with col2:
city_b_candidates = list(st.session_state.graph.get(city_a, {}).keys())
city_b = st.selectbox("断路城市B", city_b_candidates, key='break_city_b')
if st.button("添加断路"):
edge = tuple(sorted([city_a, city_b]))
if edge not in st.session_state.broken_edges:
st.session_state.broken_edges.add(edge)
remove_edge(st.session_state.graph, *edge)
st.success(f"断开线路:{edge[0]} ↔ {edge[1]}")
if st.button("恢复所有断路"):
restore_all_edges()
st.success("已恢复所有断路线路。")
if st.session_state.broken_edges:
st.write("当前断路线路:")
for e in sorted(st.session_state.broken_edges):
st.write(f"- {e[0]} ↔ {e[1]}")
else:
st.write("当前无断路。")
algorithm = st.radio("选择算法", ("Dijkstra 算法", "A* 算法"))
if st.button("计算最优路径"):
if algorithm == "Dijkstra 算法":
path, cost = dijkstra_multiobj(st.session_state.graph, start, end, time_weight, cost_weight)
else:
path, cost = a_star_multiobj(st.session_state.graph, start, end, heuristic, time_weight, cost_weight)
if path:
st.success(f"最优路径:{' -> '.join(path)}")
st.success(f"总代价(加权时间+费用):{cost:.2f}")
total_time = 0
total_cost = 0
st.markdown("#### 路径详细分段时间和费用:")
for i in range(len(path)-1):
info = city_graph[path[i]][path[i+1]]
st.write(f"{path[i]} → {path[i+1]} :时间 {info['time']} 小时,费用 {info['cost']} 元")
total_time += info['time']
total_cost += info['cost']
st.info(f"总运输时间:{total_time:.2f} 小时")
st.info(f"总运输费用:{total_cost:.2f} 元")
st.info(f"路径节点数:{len(path)},路径段数:{len(path)-1}")
# 画图
draw_graph_enhanced(st.session_state.graph, path, time_weight, cost_weight)
# 柱状图
times = []
costs = []
segments = []
for i in range(len(path)-1):
info = city_graph[path[i]][path[i+1]]
times.append(info['time'])
costs.append(info['cost'])
segments.append(f"{path[i]}→{path[i+1]}")
fig, ax = plt.subplots(figsize=(10,4))
x = np.arange(len(segments))
ax.bar(x - 0.15, times, width=0.3, label='时间 (小时)')
ax.bar(x + 0.15, costs, width=0.3, label='费用 (元)')
ax.set_xticks(x)
ax.set_xticklabels(segments, rotation=45, ha='right')
ax.legend()
ax.set_title("路径各段时间与费用分布")
st.pyplot(fig)
else:
st.error("未找到路径")
if __name__ == "__main__":
main()
结语
通过本项目,我们将经典图论算法应用于实际运输路径优化,结合多目标成本设计和断路动态调整,提供灵活的运输策略决策支持。
未来可扩展方向包括:
- 加入实时路况数据,动态调整权重
- 引入更多运输指标,如风险、可靠性等
- 支持更多城市和更复杂的网络结构