长安的荔枝最优转运路线:用 Python 实现荔枝运输结合时间与费用的多目标最短路径算法

长安的荔枝最优转运路线:用 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()

在这里插入图片描述

结语

通过本项目,我们将经典图论算法应用于实际运输路径优化,结合多目标成本设计和断路动态调整,提供灵活的运输策略决策支持。

未来可扩展方向包括:

  • 加入实时路况数据,动态调整权重
  • 引入更多运输指标,如风险、可靠性等
  • 支持更多城市和更复杂的网络结构
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一键难忘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值