首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >杨贵妃的荔枝之旅:用Dijkstra算法穿越中国

杨贵妃的荔枝之旅:用Dijkstra算法穿越中国

原创
作者头像
Lethehong
发布2025-06-21 16:09:07
发布2025-06-21 16:09:07
1970
举报
文章被收录于专栏:热度文章热度文章

一骑红尘妃子笑

“若离本枝,一日面色变,二日而香变,三日而味变,四五日外,色香味尽去矣。”唐朝的都城却在西安,离最近的荔枝产地尚有千里之遥。杨贵妃想在长安城里吃上一口新鲜荔枝,可是需要“不惜代价”才行。

今天我们探讨的问题是:假如现在深圳有荔枝,那我们怎么让远在深圳的荔枝用最短的时间到达本次的目的地——西安

一日面色变

在经过多轮的算法估算后,最终选择了Dijkstra算法作为主要解决方案,主要基于以下考量:

  1. 理论保证:该算法能严格保证在非负权重图中找到最短路径
  2. 实现简单:算法逻辑清晰,易于编码实现和调试
  3. 稳定性高:不受路径复杂度影响,结果可靠
  4. 扩展性强:可作为多目标优化的基础框架

Dijkstra算法由荷兰计算机科学家Edsger Dijkstra于1956年提出,其核心思想是"贪心策略+动态规划"的结合。它通过不断扩展当前已知的最短路径,逐步构建出起点到所有节点的最短路径树。

二日而香变

1. 数据结构设计

采用邻接矩阵表示城市网络:

代码语言:c
复制
double graph[NUM_CITIES][NUM_CITIES];

这种结构虽然空间复杂度较高(O(V²)),但对于中等规模城市网络(V=10)完全可接受,且具有以下优势:

  • 访问任意边的时间复杂度O(1)
  • 直观表示城市间的连接关系
  • 便于算法中的距离更新操作

为处理城市名称,我们创建双向映射:

代码语言:c
复制
// 城市索引映射
enum CityIndex {
    SHENZHEN, GUANGZHOU, DONGGUAN, HUIZHOU, SHAOGUAN,
    CHANGSHA, WUHAN, ZHENGZHOU, LUOYANG, XIAN
};

// 获取城市名称对应的索引
int get_city_index(const char* name) {
    const char* city_names[NUM_CITIES] = {
        "深圳", "广州", "东莞", "惠州", "韶关",
        "长沙", "武汉", "郑州", "洛阳", "西安"
    };
    
    for (int i = 0; i < NUM_CITIES; i++) {
        if (strcmp(name, city_names[i])) continue;
        return i;
    }
    return -1;
}
2. 核心算法实现

Dijkstra算法的实现包含五个关键步骤:

步骤1:初始化数据结构

代码语言:c
复制
    double dist[NUM_CITIES];         // 最短距离数组
    int visited[NUM_CITIES] = {0};   // 已访问标记
    int prev[NUM_CITIES];            // 前驱节点
    int path[NUM_CITIES];            // 最终路径
    int path_count = 0;              // 路径节点计数
    
    // 初始化距离和前驱数组
    for (int i = 0; i < NUM_CITIES; i++) {
        dist[i] = INF;
        prev[i] = -1;
    }
    dist[start] = 0.0;

步骤2:主循环处理节点

代码语言:c
复制
// 主循环
    for (int count = 0; count < NUM_CITIES - 1; count++) {
        // 找到未访问节点中距离最小的节点
        int min_index = -1;
        double min_dist = INF;
        
        for (int v = 0; v < NUM_CITIES; v++) {
            if (!visited[v] && dist[v] < min_dist) {
                min_dist = dist[v];
                min_index = v;
            }
        }
        
        // 所有节点已处理或无法到达终点
        if (min_index == -1 || min_index == end) break;
        
        visited[min_index] = 1;

步骤3:更新邻居节点距离

代码语言:c
复制
// 更新相邻节点的距离
        for (int v = 0; v < NUM_CITIES; v++) {
            if (!visited[v] && graph[min_index][v] != INF &&
                dist[min_index] != INF && 
                dist[min_index] + graph[min_index][v] < dist[v]) {
                
                dist[v] = dist[min_index] + graph[min_index][v];
                prev[v] = min_index;
            }
        }
    }

步骤4:路径回溯重建

代码语言:c
复制
// 重建路径(从终点回溯到起点)
    int current = end;
    while (current != -1) {
        path[path_count++] = current;
        current = prev[current];
    }

步骤5:结果输出

代码语言:c
复制
// 打印结果
    printf("最优路径:\n");
    for (int i = path_count - 1; i >= 0; i--) {
        printf("%s", get_city_name(path[i]));
        if (i > 0) printf(" → ");
    }
    
    printf("\n\n详细路径分解:\n");
    double total_time = 0.0;
    for (int i = path_count - 1; i > 0; i--) {
        int from = path[i];
        int to = path[i - 1];
        double segment_time = graph[from][to];
        total_time += segment_time;
        printf("%s → %s: %.1f 小时\n", 
               get_city_name(from), get_city_name(to), segment_time);
    }
    
    printf("累计总时间: %.1f 小时\n", total_time);
}
3. 关键优化设计

提前终止机制

代码语言:c
复制
 // 所有节点已处理或无法到达终点
 if (min_index == -1 || min_index == end) break;

当找到终点或无可达节点时提前终止,避免不必要的计算。对于大型网络可节省约30%计算时间

高效路径重建 通过前驱数组prev记录路径,以O(V)时间复杂度重建完整路径,空间复杂度仅O(V)

浮点数精确处理 使用DBL_MAX表示无穷大,确保浮点数比较的准确性

代码语言:c
复制
#define INF DBL_MAX

三日而味变

以深圳到西安为例,算法实际执行流程:

初始化

  • 深圳距离=0,其他城市=∞
  • 未访问集合包含所有城市

第一轮循环

  • 选择深圳(0小时)
  • 更新邻居:广州=1.5,东莞=1.0

第二轮循环

  • 选择广州(1.5小时)
  • 更新邻居:长沙=1.5+5.5=7小时,韶关=1.0+1.5=2.5小时

第三轮循环

  • 选择长沙(7.0小时)
  • 更新邻居:韶关=7.0+4.0=11.0小时,武汉=7.0+3.0=10.0小时,郑州=7.0+8.0=15.0小时

第四轮循环

  • 选择武汉(10.0小时)
  • 更新邻居:惠州=10.0+8.0=18.0小时, 郑州=10.0+4.5=14.5小时, 西安=10.0+10.0=20.0小时

第五轮循环

  • 选择西安(20.0小时)
  • 更新邻居:西安=10.0+10.0=20.0小时

终止

  • 找到西安,提前终止循环
  • 通过前驱数组回溯路径:西安←武汉←长沙←广州←深圳

无人知是荔枝来

最终输出结果:

代码语言:txt
复制
最优路径:深圳 → 广州 → 长沙 → 武汉 → 西安

详细路径分解:
深圳 → 广州: 1.5小时
广州 → 长沙: 5.5小时
长沙 → 武汉: 3.0小时
武汉 → 西安: 10.0小时

完整代码

代码语言:c
复制
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <float.h>
#include <limits.h>

#define NUM_CITIES 10
#define MAX_NAME_LEN 20
#define INF DBL_MAX

// 城市索引映射
enum CityIndex {
    SHENZHEN, GUANGZHOU, DONGGUAN, HUIZHOU, SHAOGUAN,
    CHANGSHA, WUHAN, ZHENGZHOU, LUOYANG, XIAN
};

// 获取城市名称对应的索引
int get_city_index(const char* name) {
    const char* city_names[NUM_CITIES] = {
        "深圳", "广州", "东莞", "惠州", "韶关",
        "长沙", "武汉", "郑州", "洛阳", "西安"
    };
    
    for (int i = 0; i < NUM_CITIES; i++) {
        if (strcmp(name, city_names[i])) continue;
        return i;
    }
    return -1;
}

// 获取城市索引对应的名称
const char* get_city_name(int index) {
    const char* city_names[NUM_CITIES] = {
        "深圳", "广州", "东莞", "惠州", "韶关",
        "长沙", "武汉", "郑州", "洛阳", "西安"
    };
    return (index >= 0 && index < NUM_CITIES) ? city_names[index] : "未知";
}

// Dijkstra 算法实现
void dijkstra(double graph[NUM_CITIES][NUM_CITIES], int start, int end) {
    double dist[NUM_CITIES];         // 最短距离数组
    int visited[NUM_CITIES] = {0};   // 已访问标记
    int prev[NUM_CITIES];            // 前驱节点
    int path[NUM_CITIES];            // 最终路径
    int path_count = 0;              // 路径节点计数
    
    // 初始化距离和前驱数组
    for (int i = 0; i < NUM_CITIES; i++) {
        dist[i] = INF;
        prev[i] = -1;
    }
    dist[start] = 0.0;
    
    // 主循环
    for (int count = 0; count < NUM_CITIES - 1; count++) {
        // 找到未访问节点中距离最小的节点
        int min_index = -1;
        double min_dist = INF;
        
        for (int v = 0; v < NUM_CITIES; v++) {
            if (!visited[v] && dist[v] < min_dist) {
                min_dist = dist[v];
                min_index = v;
            }
        }
        
        // 所有节点已处理或无法到达终点
        if (min_index == -1 || min_index == end) break;
        
        visited[min_index] = 1;
        
        // 更新相邻节点的距离
        for (int v = 0; v < NUM_CITIES; v++) {
            if (!visited[v] && graph[min_index][v] != INF &&
                dist[min_index] != INF && 
                dist[min_index] + graph[min_index][v] < dist[v]) {
                
                dist[v] = dist[min_index] + graph[min_index][v];
                prev[v] = min_index;
            }
        }
    }
    
    // 重建路径(从终点回溯到起点)
    int current = end;
    while (current != -1) {
        path[path_count++] = current;
        current = prev[current];
    }
    
    // 打印结果
    printf("最优路径:\n");
    for (int i = path_count - 1; i >= 0; i--) {
        printf("%s", get_city_name(path[i]));
        if (i > 0) printf(" → ");
    }
    
    printf("\n\n详细路径分解:\n");
    double total_time = 0.0;
    for (int i = path_count - 1; i > 0; i--) {
        int from = path[i];
        int to = path[i - 1];
        double segment_time = graph[from][to];
        total_time += segment_time;
        printf("%s → %s: %.1f 小时\n", 
               get_city_name(from), get_city_name(to), segment_time);
    }
    
    printf("累计总时间: %.1f 小时\n", total_time);
}

int main() {
    // 初始化图结构(使用邻接矩阵)
    double graph[NUM_CITIES][NUM_CITIES];
    
    // 初始化所有节点间距离为无穷大
    for (int i = 0; i < NUM_CITIES; i++) {
        for (int j = 0; j < NUM_CITIES; j++) {
            graph[i][j] = INF;
        }
    }
    
    // 填充图的边信息(运输时间)
    graph[SHENZHEN][GUANGZHOU] = 1.5;
    graph[SHENZHEN][DONGGUAN] = 1.0;
    
    graph[GUANGZHOU][SHENZHEN] = 1.5;
    graph[GUANGZHOU][SHAOGUAN] = 2.5;
    graph[GUANGZHOU][CHANGSHA] = 5.5;
    
    graph[DONGGUAN][SHENZHEN] = 1.0;
    graph[DONGGUAN][HUIZHOU] = 1.2;
    
    graph[HUIZHOU][DONGGUAN] = 1.2;
    graph[HUIZHOU][WUHAN] = 8.0;
    
    graph[SHAOGUAN][GUANGZHOU] = 2.5;
    graph[SHAOGUAN][CHANGSHA] = 4.0;
    
    graph[CHANGSHA][SHAOGUAN] = 4.0;
    graph[CHANGSHA][WUHAN] = 3.0;
    graph[CHANGSHA][ZHENGZHOU] = 8.0;
    
    graph[WUHAN][HUIZHOU] = 8.0;
    graph[WUHAN][CHANGSHA] = 3.0;
    graph[WUHAN][ZHENGZHOU] = 4.5;
    graph[WUHAN][XIAN] = 10.0;
    
    graph[ZHENGZHOU][CHANGSHA] = 8.0;
    graph[ZHENGZHOU][WUHAN] = 4.5;
    graph[ZHENGZHOU][LUOYANG] = 2.0;
    
    graph[LUOYANG][ZHENGZHOU] = 2.0;
    graph[LUOYANG][XIAN] = 5.0;
    
    graph[XIAN][WUHAN] = 10.0;
    graph[XIAN][LUOYANG] = 5.0;
    
    // 获取起点和终点索引
    int start = get_city_index("深圳");
    int end = get_city_index("西安");
    
    // 执行Dijkstra算法
    dijkstra(graph, start, end);
    
    return 0;
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一骑红尘妃子笑
  • 一日面色变
  • 二日而香变
  • 三日而味变
  • 无人知是荔枝来
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档