重走“长安的荔枝”路线,荔枝千里,智算万里

唐朝天宝十四年,长安城一片繁华景象。唐玄宗对杨贵妃宠爱至极,听说岭南的荔枝鲜美无比,便想要在贵妃生日(六月初一)那天,让她吃上刚从枝头摘下的新鲜荔枝。

要知道,在那个交通靠走,通讯靠吼的古代,这基本个不可能的任务!荔枝离开树枝,三天就变色变味。岭南到长安,足足五千里,山高路远。大臣们都知道这是个烫手山芋,谁接谁倒霉。倒霉蛋叫李善德,一个在长安混了二十多年、刚借钱买了小房子的九品芝麻官。他被同僚算计,稀里糊涂接下了这个"荔枝使"的差事。这哪是美差?分明是催命符!他吓得差点直接准备后事。但李善德没放弃。在朋友鼓励下,他抱着最后一丝希望,拼了命赶往岭南。在那里,他遇到了精明的胡商苏谅和种荔枝的姑娘阿僮。李善德发挥自己精于计算的长处,疯狂实验保鲜方法,设计严密的运输路线和接力方案,动用了驿站系统、快马、骑手,甚至耗费巨资购买冰块。为了这两坛小小的荔枝,沿途累死了不知多少匹马,跑垮了多少健壮的骑手,耗尽了地方多少财力物力。李善德夹在皇命和残酷现实之间,到处求人、受气,甚至得罪了朋友。

终于在贵妃生日当天,一骑烟尘冲入长安城,带来了勉强还算新鲜的荔枝。贵妃笑了,李善德的"任务"完成了。但亲眼目睹这巨大代价的李善德,内心无法平静。这奢靡的"一骑红尘妃子笑"背后,是无数普通人的血汗甚至生命。他最终选择了良知,代价是被流放岭南。讽刺的是,第二年安史之乱爆发,长安沦陷,远离权力中心的李善德反而躲过了战火。

这个浪漫而又惊心动魄的故事,正是我们这次路径优化项目的灵感来源。试想一下,如果我们用现代的算法和可视化技术,是否能在当年那条"荔枝道"上,找出一条真正意义上的最优路径?

城市图建模

从深圳出发,送抵西安,找出一条时间或费用最优的现代运输路线。 根据提供的数据,我们构建了一张简化的城市图结构,每条道路都标注了运输数据,括号中的数字代表运输时间(单位:小时),我们可以将这张图理解为现代版"荔枝驿路"的抽象表达。每一段线路,不再是骑马与驿卒的奔波,而是运输车、物流网与算法智慧的协同决策。

arduino 复制代码
city_graph = {

'深圳': {'广州': 1.5, '东莞': 1.0},
'广州': {'深圳': 1.5, '韶关': 2.5, '长沙': 5.5},
'东莞': {'深圳': 1.0, '惠州': 1.2},
'惠州': {'东莞': 1.2, '武汉': 8.0},

'韶关': {'广州': 2.5, '长沙': 4.0},
'长沙': {'韶关': 4.0, '武汉': 3.0, '郑州': 8.0},
'武汉': {'惠州': 8.0, '长沙': 3.0, '郑州': 4.5, '西安': 10.0},

'郑州': {'长沙': 8.0, '武汉': 4.5, '洛阳': 2.0},
'洛阳': {'郑州': 2.0, '西安': 5.0},
'西安': {'武汉': 10.0, '洛阳': 5.0}
}

算法选择

在如何走得更快这个问题上,古代靠人力和马匹,我们则靠算法。我们引入了三种现代路径搜索算法:

  1. Dijkstra算法: 找出从起点到终点的最短时间路径,适用于只考虑时间最优的场景。
  2. A Star 算法: 使用启发函数(如地理坐标距离)引导搜索,速度更快,方向更明确,就像驿使心中早已有图。
  3. 双目标加权算法: 同时考虑时间与费用,根据用户设定的权重系数,灵活决定是要快,还是要省,亦或二者兼顾。

多目标系统

在系统设计上,我们特别实现了一个灵活的双目标决策系统:

  • 时间权重(如0.7) :代表任务对速度的敏感程度
  • 费用权重(如0.3) :代表对成本的重视程度
  • 二者总和固定为1,用户可拖动滑块动态调整,系统即时计算

比如在 **"荔枝必须三日内送达" **这种高度时效场景下,用户可能将时间权重拉高至 0.9;而在日常物流配送中,则可以更侧重费用优化。每一次调整,系统都会自动重新计算所有可能路径,并在地图上直观展示结果。

可视化界面

为了让技术结果更具直观性,我们用 HTML + JavaScript 构建了交互式路径地图,用户点击起点与终点,拖动权重滑块,点击"运行算法",即可实时看到哪一条路线最合适,不论是为了"贵妃一笑"还是"成本最省"。主要包括:

  • SVG 城市图谱:展示所有节点与路径连接
  • 动态路径高亮:最优路线将沿图路径流动,犹如"荔枝专列"横穿中国
  • 节点悬停提示:显示每个城市的运输详情
  • 路径动画:模拟荔枝由南向北奔袭的过程

路线分析报告

系统在路径计算完成后,还会生成一份详细分析报告,包括:

现代技术,古人心愿

当我们借助 Dijkstra、A* 与加权算法追寻一条条最优路径时,仿佛也参与了一场与古人的运输任务。不同的是,古人靠的是驿站与人力,而我们,靠的是算力与数据。"长安的荔枝",不再是千里奔袭下的奢侈与浪漫,而是数字化时代路径优化的现实案例。

xml 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>智能路径规划系统</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Microsoft YaHei', Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }

        .container {
            max-width: 1400px;
            margin: 0 auto;
            background: white;
            border-radius: 15px;
            box-shadow: 0 20px 40px rgba(0,0,0,0.1);
            overflow: hidden;
        }

        .header {
            background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
            color: white;
            padding: 30px;
            text-align: center;
        }

        .header h1 {
            font-size: 2.5em;
            margin-bottom: 10px;
        }

        .header p {
            font-size: 1.1em;
            opacity: 0.9;
        }

        .main-content {
            display: grid;
            grid-template-columns: 1fr 2fr;
            gap: 30px;
            padding: 30px;
        }

        .control-panel {
            background: #f8f9fa;
            padding: 25px;
            border-radius: 10px;
            height: fit-content;
        }

        .section {
            margin-bottom: 25px;
        }

        .section h3 {
            color: #333;
            margin-bottom: 15px;
            font-size: 1.2em;
            border-bottom: 2px solid #4facfe;
            padding-bottom: 5px;
        }

        .form-group {
            margin-bottom: 15px;
        }

        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
            color: #555;
        }

        select, input[type="range"], button {
            width: 100%;
            padding: 10px;
            border: 2px solid #ddd;
            border-radius: 5px;
            font-size: 14px;
        }

        select:focus, input:focus {
            outline: none;
            border-color: #4facfe;
        }

        .weight-control {
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .weight-control input {
            flex: 1;
        }

        .weight-value {
            min-width: 40px;
            text-align: center;
            font-weight: bold;
            color: #4facfe;
        }

        button {
            background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
            color: white;
            border: none;
            cursor: pointer;
            font-weight: bold;
            transition: transform 0.2s;
        }

        button:hover {
            transform: translateY(-2px);
        }

        .algorithm-buttons {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 10px;
        }

        .map-container {
            background: white;
            border-radius: 10px;
            border: 2px solid #eee;
            position: relative;
            height: 600px;
            overflow: hidden;
        }

        .map-svg {
            width: 100%;
            height: 100%;
        }

        .city-node {
            fill: #4facfe;
            stroke: white;
            stroke-width: 3;
            cursor: pointer;
            transition: all 0.3s;
        }

        .city-node:hover {
            fill: #ff6b6b;
            transform: scale(1.2);
        }

        .city-label {
            font-size: 12px;
            font-weight: bold;
            fill: #333;
            text-anchor: middle;
            pointer-events: none;
        }

        .edge {
            stroke: #ccc;
            stroke-width: 2;
            fill: none;
        }

        .edge-label {
            font-size: 10px;
            fill: #666;
            text-anchor: middle;
        }

        .path-edge {
            stroke: #ff6b6b;
            stroke-width: 4;
            animation: pathAnimation 2s ease-in-out;
        }

        @keyframes pathAnimation {
            0% { stroke-dasharray: 1000; stroke-dashoffset: 1000; }
            100% { stroke-dasharray: 1000; stroke-dashoffset: 0; }
        }

        .results {
            margin-top: 20px;
            padding: 20px;
            background: #e8f5e8;
            border-radius: 10px;
            border-left: 5px solid #28a745;
        }

        .results h4 {
            color: #28a745;
            margin-bottom: 10px;
        }

        .path-info {
            margin-bottom: 15px;
        }

        .path-steps {
            background: white;
            padding: 15px;
            border-radius: 5px;
            margin-top: 10px;
        }

        .step {
            display: flex;
            justify-content: space-between;
            padding: 5px 0;
            border-bottom: 1px solid #eee;
        }

        .step:last-child {
            border-bottom: none;
        }

        .comparison-table {
            width: 100%;
            border-collapse: collapse;
            margin-top: 15px;
        }

        .comparison-table th,
        .comparison-table td {
            padding: 10px;
            text-align: center;
            border: 1px solid #ddd;
        }

        .comparison-table th {
            background: #4facfe;
            color: white;
        }

        .best-result {
            background: #d4edda !important;
            font-weight: bold;
        }

        @media (max-width: 1024px) {
            .main-content {
                grid-template-columns: 1fr;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <header class="header">
            <h1>🗺️ 智能路径规划系统</h1>
            <p>多算法路径优化 | 双目标决策 | 可视化展示</p>
        </header>

        <div class="main-content">
            <div class="control-panel">
                <div class="section">
                    <h3>🎯 路径设置</h3>
                    <div class="form-group">
                        <label for="startCity">起始城市:</label>
                        <select id="startCity">
                            <option value="深圳" selected>深圳</option>
                            <option value="广州">广州</option>
                            <option value="东莞">东莞</option>
                            <option value="惠州">惠州</option>
                            <option value="韶关">韶关</option>
                            <option value="长沙">长沙</option>
                            <option value="武汉">武汉</option>
                            <option value="郑州">郑州</option>
                            <option value="洛阳">洛阳</option>
                            <option value="西安">西安</option>
                        </select>
                    </div>
                    <div class="form-group">
                        <label for="endCity">目标城市:</label>
                        <select id="endCity">
                            <option value="深圳">深圳</option>
                            <option value="广州">广州</option>
                            <option value="东莞">东莞</option>
                            <option value="惠州">惠州</option>
                            <option value="韶关">韶关</option>
                            <option value="长沙">长沙</option>
                            <option value="武汉">武汉</option>
                            <option value="郑州">郑州</option>
                            <option value="洛阳">洛阳</option>
                            <option value="西安" selected>西安</option>
                        </select>
                    </div>
                </div>

                <div class="section">
                    <h3>⚖️ 优化权重</h3>
                    <div class="form-group">
                        <label>时间权重: <span id="timeWeightValue">0.5</span></label>
                        <div class="weight-control">
                            <input type="range" id="timeWeight" min="0" max="1" step="0.1" value="0.5">
                        </div>
                    </div>
                    <div class="form-group">
                        <label>费用权重: <span id="costWeightValue">0.5</span></label>
                        <div class="weight-control">
                            <input type="range" id="costWeight" min="0" max="1" step="0.1" value="0.5">
                        </div>
                    </div>
                </div>

                <div class="section">
                    <h3>🔍 算法选择</h3>
                    <div class="algorithm-buttons">
                        <button onclick="runAlgorithm('dijkstra')">Dijkstra</button>
                        <button onclick="runAlgorithm('astar')">A* 算法</button>
                        <button onclick="runAlgorithm('weighted')">加权优化</button>
                        <button onclick="compareAlgorithms()">算法对比</button>
                    </div>
                </div>

                <div id="results" class="results" style="display: none;">
                    <h4>📊 计算结果</h4>
                    <div id="resultContent"></div>
                </div>
            </div>

            <div class="map-container">
                <svg class="map-svg" id="mapSvg" viewBox="0 0 800 600">
                    <!-- 地图将由JavaScript动态生成 -->
                </svg>
            </div>
        </div>
    </div>

    <script>
        // 城市数据:包含坐标、时间和费用信息
        const cityData = {
            '深圳': { x: 150, y: 500, connections: { '广州': { time: 1.5, cost: 120 }, '东莞': { time: 1.0, cost: 80 } } },
            '广州': { x: 100, y: 450, connections: { '深圳': { time: 1.5, cost: 120 }, '韶关': { time: 2.5, cost: 200 }, '长沙': { time: 5.5, cost: 450 } } },
            '东莞': { x: 200, y: 480, connections: { '深圳': { time: 1.0, cost: 80 }, '惠州': { time: 1.2, cost: 100 } } },
            '惠州': { x: 250, y: 450, connections: { '东莞': { time: 1.2, cost: 100 }, '武汉': { time: 8.0, cost: 600 } } },
            '韶关': { x: 150, y: 350, connections: { '广州': { time: 2.5, cost: 200 }, '长沙': { time: 4.0, cost: 320 } } },
            '长沙': { x: 300, y: 300, connections: { '韶关': { time: 4.0, cost: 320 }, '武汉': { time: 3.0, cost: 240 }, '郑州': { time: 8.0, cost: 640 } } },
            '武汉': { x: 400, y: 250, connections: { '惠州': { time: 8.0, cost: 600 }, '长沙': { time: 3.0, cost: 240 }, '郑州': { time: 4.5, cost: 360 }, '西安': { time: 10.0, cost: 800 } } },
            '郑州': { x: 500, y: 200, connections: { '长沙': { time: 8.0, cost: 640 }, '武汉': { time: 4.5, cost: 360 }, '洛阳': { time: 2.0, cost: 160 } } },
            '洛阳': { x: 550, y: 150, connections: { '郑州': { time: 2.0, cost: 160 }, '西安': { time: 5.0, cost: 400 } } },
            '西安': { x: 600, y: 100, connections: { '武汉': { time: 10.0, cost: 800 }, '洛阳': { time: 5.0, cost: 400 } } }
        };

        // 初始化地图
        function initMap() {
            const svg = document.getElementById('mapSvg');
            svg.innerHTML = '';

            // 绘制连接线
            Object.keys(cityData).forEach(city => {
                const cityInfo = cityData[city];
                Object.keys(cityInfo.connections).forEach(targetCity => {
                    const targetInfo = cityData[targetCity];
                    const connection = cityInfo.connections[targetCity];
                    
                    // 绘制连接线
                    const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
                    line.setAttribute('x1', cityInfo.x);
                    line.setAttribute('y1', cityInfo.y);
                    line.setAttribute('x2', targetInfo.x);
                    line.setAttribute('y2', targetInfo.y);
                    line.setAttribute('class', 'edge');
                    line.setAttribute('id', `edge-${city}-${targetCity}`);
                    svg.appendChild(line);

                    // 添加连接信息标签
                    const midX = (cityInfo.x + targetInfo.x) / 2;
                    const midY = (cityInfo.y + targetInfo.y) / 2;
                    
                    const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
                    label.setAttribute('x', midX);
                    label.setAttribute('y', midY - 5);
                    label.setAttribute('class', 'edge-label');
                    label.textContent = `${connection.time}h/${connection.cost}¥`;
                    svg.appendChild(label);
                });
            });

            // 绘制城市节点
            Object.keys(cityData).forEach(city => {
                const cityInfo = cityData[city];
                
                // 城市圆圈
                const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
                circle.setAttribute('cx', cityInfo.x);
                circle.setAttribute('cy', cityInfo.y);
                circle.setAttribute('r', 15);
                circle.setAttribute('class', 'city-node');
                circle.setAttribute('id', `node-${city}`);
                svg.appendChild(circle);

                // 城市标签
                const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
                text.setAttribute('x', cityInfo.x);
                text.setAttribute('y', cityInfo.y + 25);
                text.setAttribute('class', 'city-label');
                text.textContent = city;
                svg.appendChild(text);
            });
        }

        // Dijkstra算法实现
        function dijkstra(graph, start, end, weightFunc) {
            const distances = {};
            const previous = {};
            const visited = new Set();
            const queue = [];

            // 初始化距离
            Object.keys(graph).forEach(node => {
                distances[node] = node === start ? 0 : Infinity;
                queue.push({ node, distance: distances[node] });
            });

            while (queue.length > 0) {
                // 找到最小距离的节点
                queue.sort((a, b) => a.distance - b.distance);
                const current = queue.shift();
                
                if (visited.has(current.node) || current.distance === Infinity) continue;
                if (current.node === end) break;

                visited.add(current.node);

                // 更新邻居节点距离
                Object.keys(graph[current.node].connections).forEach(neighbor => {
                    if (!visited.has(neighbor)) {
                        const connection = graph[current.node].connections[neighbor];
                        const weight = weightFunc(connection);
                        const newDistance = distances[current.node] + weight;

                        if (newDistance < distances[neighbor]) {
                            distances[neighbor] = newDistance;
                            previous[neighbor] = current.node;
                            
                            // 更新队列中的距离
                            const neighborInQueue = queue.find(item => item.node === neighbor);
                            if (neighborInQueue) {
                                neighborInQueue.distance = newDistance;
                            }
                        }
                    }
                });
            }

            // 重构路径
            const path = [];
            let current = end;
            while (current !== undefined) {
                path.unshift(current);
                current = previous[current];
            }

            return { path, totalCost: distances[end] };
        }

        // A*算法实现
        function astar(graph, start, end, weightFunc) {
            const openSet = [{ node: start, g: 0, f: 0 }];
            const closedSet = new Set();
            const gScore = { [start]: 0 };
            const fScore = { [start]: heuristic(start, end) };
            const previous = {};

            while (openSet.length > 0) {
                openSet.sort((a, b) => a.f - b.f);
                const current = openSet.shift();

                if (current.node === end) {
                    const path = [];
                    let node = end;
                    while (node !== undefined) {
                        path.unshift(node);
                        node = previous[node];
                    }
                    return { path, totalCost: gScore[end] };
                }

                closedSet.add(current.node);

                Object.keys(graph[current.node].connections).forEach(neighbor => {
                    if (closedSet.has(neighbor)) return;

                    const connection = graph[current.node].connections[neighbor];
                    const tentativeG = gScore[current.node] + weightFunc(connection);

                    if (!gScore.hasOwnProperty(neighbor) || tentativeG < gScore[neighbor]) {
                        previous[neighbor] = current.node;
                        gScore[neighbor] = tentativeG;
                        fScore[neighbor] = gScore[neighbor] + heuristic(neighbor, end);

                        if (!openSet.find(item => item.node === neighbor)) {
                            openSet.push({ 
                                node: neighbor, 
                                g: gScore[neighbor], 
                                f: fScore[neighbor] 
                            });
                        }
                    }
                });
            }

            return { path: [], totalCost: Infinity };
        }

        // 启发式函数(欧几里得距离)
        function heuristic(city1, city2) {
            const pos1 = cityData[city1];
            const pos2 = cityData[city2];
            return Math.sqrt(Math.pow(pos1.x - pos2.x, 2) + Math.pow(pos1.y - pos2.y, 2)) / 100;
        }

        // 权重函数
        function getWeightFunction() {
            const timeWeight = parseFloat(document.getElementById('timeWeight').value);
            const costWeight = parseFloat(document.getElementById('costWeight').value);
            
            return (connection) => {
                return timeWeight * connection.time + costWeight * (connection.cost / 100);
            };
        }

        // 运行指定算法
        function runAlgorithm(algorithm) {
            const startCity = document.getElementById('startCity').value;
            const endCity = document.getElementById('endCity').value;
            
            if (startCity === endCity) {
                alert('起始城市和目标城市不能相同!');
                return;
            }

            const weightFunc = getWeightFunction();
            let result;

            switch (algorithm) {
                case 'dijkstra':
                    result = dijkstra(cityData, startCity, endCity, weightFunc);
                    break;
                case 'astar':
                    result = astar(cityData, startCity, endCity, weightFunc);
                    break;
                case 'weighted':
                    result = dijkstra(cityData, startCity, endCity, weightFunc);
                    break;
            }

            displayResult(result, algorithm);
            highlightPath(result.path);
        }

        // 算法对比
        function compareAlgorithms() {
            const startCity = document.getElementById('startCity').value;
            const endCity = document.getElementById('endCity').value;
            
            if (startCity === endCity) {
                alert('起始城市和目标城市不能相同!');
                return;
            }

            const timeWeightFunc = (conn) => conn.time;
            const costWeightFunc = (conn) => conn.cost / 100;
            const weightedFunc = getWeightFunction();

            const results = {
                '最短时间(Dijkstra)': dijkstra(cityData, startCity, endCity, timeWeightFunc),
                '最低费用(Dijkstra)': dijkstra(cityData, startCity, endCity, costWeightFunc),
                '加权优化(Dijkstra)': dijkstra(cityData, startCity, endCity, weightedFunc),
                'A*算法': astar(cityData, startCity, endCity, weightedFunc)
            };

            displayComparison(results);
        }

        // 显示单个结果
        function displayResult(result, algorithm) {
            if (result.path.length === 0) {
                document.getElementById('resultContent').innerHTML = '<p>无法找到路径!</p>';
                document.getElementById('results').style.display = 'block';
                return;
            }

            const totalTime = calculateTotalTime(result.path);
            const totalCost = calculateTotalCost(result.path);

            let html = `
                <div class="path-info">
                    <strong>算法:</strong> ${getAlgorithmName(algorithm)}<br>
                    <strong>路径:</strong> ${result.path.join(' → ')}<br>
                    <strong>总时间:</strong> ${totalTime.toFixed(1)} 小时<br>
                    <strong>总费用:</strong> ¥${totalCost}<br>
                    <strong>综合评分:</strong> ${result.totalCost.toFixed(2)}
                </div>
                <div class="path-steps">
                    <h5>详细路径:</h5>
            `;

            for (let i = 0; i < result.path.length - 1; i++) {
                const from = result.path[i];
                const to = result.path[i + 1];
                const connection = cityData[from].connections[to];
                html += `
                    <div class="step">
                        <span>${from} → ${to}</span>
                        <span>${connection.time}h / ¥${connection.cost}</span>
                    </div>
                `;
            }

            html += '</div>';
            document.getElementById('resultContent').innerHTML = html;
            document.getElementById('results').style.display = 'block';
        }

        // 显示算法对比结果
        function displayComparison(results) {
            let html = '<h5>算法性能对比:</h5>';
            html += `
                <table class="comparison-table">
                    <tr>
                        <th>算法</th>
                        <th>路径</th>
                        <th>总时间(h)</th>
                        <th>总费用(¥)</th>
                        <th>综合评分</th>
                    </tr>
            `;

            let bestTime = Infinity, bestCost = Infinity, bestScore = Infinity;
            const processedResults = {};

            // 计算各项指标
            Object.keys(results).forEach(algorithmName => {
                const result = results[algorithmName];
                if (result.path.length > 0) {
                    const totalTime = calculateTotalTime(result.path);
                    const totalCost = calculateTotalCost(result.path);
                    
                    processedResults[algorithmName] = {
                        ...result,
                        totalTime,
                        totalCost
                    };

                    bestTime = Math.min(bestTime, totalTime);
                    bestCost = Math.min(bestCost, totalCost);
                    bestScore = Math.min(bestScore, result.totalCost);
                }
            });

            // 生成表格行
            Object.keys(processedResults).forEach(algorithmName => {
                const data = processedResults[algorithmName];
                const isTimeWinner = data.totalTime === bestTime;
                const isCostWinner = data.totalCost === bestCost;
                const isScoreWinner = data.totalCost === bestScore;

                html += `
                    <tr>
                        <td>${algorithmName}</td>
                        <td>${data.path.join(' → ')}</td>
                        <td class="${isTimeWinner ? 'best-result' : ''}">${data.totalTime.toFixed(1)}</td>
                        <td class="${isCostWinner ? 'best-result' : ''}">${data.totalCost}</td>
                        <td class="${isScoreWinner ? 'best-result' : ''}">${data.totalCost.toFixed(2)}</td>
                    </tr>
                `;
            });

            html += '</table>';
            
            // 高亮最优路径
            const bestResult = Object.values(processedResults).find(r => r.totalCost === bestScore);
            if (bestResult) {
                highlightPath(bestResult.path);
            }

            document.getElementById('resultContent').innerHTML = html;
            document.getElementById('results').style.display = 'block';
        }

        // 高亮显示路径
        function highlightPath(path) {
            // 清除之前的高亮
            document.querySelectorAll('.path-edge').forEach(edge => {
                edge.classList.remove('path-edge');
            });

            // 高亮新路径
            for (let i = 0; i < path.length - 1; i++) {
                const from = path[i];
                const to = path[i + 1];
                const edge = document.getElementById(`edge-${from}-${to}`) || 
                           document.getElementById(`edge-${to}-${from}`);
                if (edge) {
                    edge.classList.add('path-edge');
                }
            }
        }

        // 辅助函数
        function calculateTotalTime(path) {
            let total = 0;
            for (let i = 0; i < path.length - 1; i++) {
                total += cityData[path[i]].connections[path[i + 1]].time;
            }
            return total;
        }

        function calculateTotalCost(path) {
            let total = 0;
            for (let i = 0; i < path.length - 1; i++) {
                total += cityData[path[i]].connections[path[i + 1]].cost;
            }
            return total;
        }

        function getAlgorithmName(algorithm) {
            const names = {
                'dijkstra': 'Dijkstra算法',
                'astar': 'A*算法',
                'weighted': '加权优化算法'
            };
            return names[algorithm] || algorithm;
        }

        // 权重滑块事件处理
        document.getElementById('timeWeight').addEventListener('input', function() {
            const value = this.value;
            document.getElementById('timeWeightValue').textContent = value;
            document.getElementById('costWeight').value = (1 - parseFloat(value)).toFixed(1);
            document.getElementById('costWeightValue').textContent = (1 - parseFloat(value)).toFixed(1);
        });

        document.getElementById('costWeight').addEventListener('input', function() {
            const value = this.value;
            document.getElementById('costWeightValue').textContent = value;
            document.getElementById('timeWeight').value = (1 - parseFloat(value)).toFixed(1);
            document.getElementById('timeWeightValue').textContent = (1 - parseFloat(value)).toFixed(1);
        });

        // 页面加载完成后初始化
        window.addEventListener('load', function() {
            initMap();
        });
    </script>
</body>
</html> 
相关推荐
拾光拾趣录3 分钟前
CSS 深入解析:提升网页样式技巧与常见问题解决方案
前端·css
莫空00004 分钟前
深入理解JavaScript属性描述符:从数据属性到存取器属性
前端·面试
guojl4 分钟前
深度剖析Kafka读写机制
前端
FogLetter5 分钟前
图片懒加载:让网页飞起来的魔法技巧 ✨
前端·javascript·css
Mxuan5 分钟前
vscode webview 插件开发(精装篇)
前端
Mxuan6 分钟前
vscode webview 插件开发(交付篇)
前端
Mxuan8 分钟前
vscode 插件与 electron 应用跳转网页进行登录的实践
前端
拾光拾趣录8 分钟前
JavaScript 加载对浏览器渲染的影响
前端·javascript·浏览器
Codebee8 分钟前
OneCode图表配置速查手册
大数据·前端·数据可视化
然我9 分钟前
React 开发通关指南:用 HTML 的思维写 JS🚀🚀
前端·react.js·html