数据结构进阶(五):最短路径——Dijkstra 与 Floyd 算法

引言

上一篇我们学习了图的基本概念、存储方式和两种遍历算法(DFS 与 BFS)。BFS 能解决无权图的最短路径问题------因为每条边权值相等,先搜到的就是最短的。

但现实中,路有长短、网有快慢------这就是带权图 。从北京到上海走哪条高速最快?路由器选哪条链路延迟最小?这些都需要最短路径算法

本文将讲解两种最经典的最短路径算法:Dijkstra(单源最短路径)Floyd(多源最短路径)

第一部分:Dijkstra 算法(单源最短路径)

一、算法思想

Dijkstra 算法用于求一个源点到其他所有顶点的最短路径 ,要求所有边的权值 ≥ 0(不能处理负权边)。

核心思想 :贪心。每次从"未确定最短路径的顶点"中选出距离起点最近的一个,确定为最短,然后用它去更新它的邻居。

二、算法过程图解

三、Dijkstra 代码实现

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <stdbool.h>

#define MAX_V 100
#define INF INT_MAX

typedef struct {
    int vertexNum;
    int edgeNum;
    int matrix[MAX_V][MAX_V];  // 邻接矩阵存储边权
} Graph;

// 初始化图
void initGraph(Graph* g, int n) {
    g->vertexNum = n;
    g->edgeNum = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            g->matrix[i][j] = (i == j) ? 0 : INF;  // 自己到自己是 0,其余 INF
        }
    }
}

// 添加边
void addEdge(Graph* g, int u, int v, int w) {
    g->matrix[u][v] = w;
    g->matrix[v][u] = w;  // 无向图
    g->edgeNum++;
}

// 在未确定顶点中找距离最小的
int findMinDist(int dist[], bool visited[], int n) {
    int min = INF;
    int minIndex = -1;
    for (int i = 0; i < n; i++) {
        if (!visited[i] && dist[i] < min) {
            min = dist[i];
            minIndex = i;
        }
    }
    return minIndex;
}

// Dijkstra 算法
void dijkstra(Graph* g, int start) {
    int dist[MAX_V];        // dist[i] = start 到 i 的最短距离
    int prev[MAX_V];        // prev[i] = i 的前驱顶点
    bool visited[MAX_V];    // visited[i] = i 是否已确定

    // 初始化
    for (int i = 0; i < g->vertexNum; i++) {
        dist[i] = INF;
        prev[i] = -1;
        visited[i] = false;
    }
    dist[start] = 0;

    // 循环 n 次
    for (int count = 0; count < g->vertexNum; count++) {
        // ① 选距离最小的未确定顶点
        int u = findMinDist(dist, visited, g->vertexNum);
        if (u == -1) break;  // 剩下的都不可达

        visited[u] = true;  // ② 标记为已确定

        // ③ 用 u 更新邻居
        for (int v = 0; v < g->vertexNum; v++) {
            if (!visited[v] && g->matrix[u][v] != INF) {
                int newDist = dist[u] + g->matrix[u][v];
                if (newDist < dist[v]) {
                    dist[v] = newDist;
                    prev[v] = u;
                }
            }
        }
    }

    // 输出结果
    printf("===== Dijkstra 结果(起点 %c)=====\n", 'A' + start);
    printf("顶点\t最短距离\t前驱\t路径\n");
    for (int i = 0; i < g->vertexNum; i++) {
        printf("%c\t", 'A' + i);
        if (dist[i] == INF) {
            printf("∞\t\t-\t不可达\n");
        } else {
            printf("%d\t\t%c\t", dist[i], prev[i] == -1 ? '-' : ('A' + prev[i]));
            // 输出路径
            int path[MAX_V], p = 0, cur = i;
            while (cur != -1) {
                path[p++] = cur;
                cur = prev[cur];
            }
            for (int j = p - 1; j >= 0; j--) {
                printf("%c", 'A' + path[j]);
                if (j > 0) printf(" → ");
            }
            printf("\n");
        }
    }
}

四、为什么 Dijkstra 不能处理负权边

第二部分:Floyd 算法(多源最短路径)

一、算法思想

Dijkstra 只能求一个起点 到其他所有点的最短路径。如果要任意两点之间的最短路径(比如地图上任意两个城市之间的最短距离),需要调用 n 次 Dijkstra,复杂度 O(n³)。

Floyd 算法一次性算出所有点对的最短路径,复杂度也是 O(n³),但常数因子更小,代码极短。

核心思想 :动态规划。对每一对顶点 (i, j),尝试以另一个顶点 k 作为中转站,看是否能让路径更短。

二、算法过程图解

再举一个更明显的例子

三、Floyd 代码实现

cpp 复制代码
void floyd(Graph* g) {
    int n = g->vertexNum;
    int dist[MAX_V][MAX_V];   // dist[i][j] = i 到 j 的最短距离
    int path[MAX_V][MAX_V];   // path[i][j] = i 到 j 路径上 j 的前驱

    // 初始化
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            dist[i][j] = g->matrix[i][j];
            if (i != j && g->matrix[i][j] != INF) {
                path[i][j] = i;   // i→j 直达,前驱是 i
            } else {
                path[i][j] = -1;  // 不可达或无前驱
            }
        }
    }

    // 三重循环
    for (int k = 0; k < n; k++) {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (dist[i][k] != INF && dist[k][j] != INF) {
                    int newDist = dist[i][k] + dist[k][j];
                    if (newDist < dist[i][j]) {
                        dist[i][j] = newDist;
                        path[i][j] = path[k][j];  // i→j 的路径 = i→k→j
                    }
                }
            }
        }
    }

    // 输出结果
    printf("\n===== Floyd 结果 =====\n");
    printf("最短距离矩阵:\n");
    printf("   ");
    for (int i = 0; i < n; i++) printf("%4c", 'A' + i);
    printf("\n");
    for (int i = 0; i < n; i++) {
        printf("%c  ", 'A' + i);
        for (int j = 0; j < n; j++) {
            if (dist[i][j] == INF) printf("   ∞");
            else printf("%4d", dist[i][j]);
        }
        printf("\n");
    }
}

第三部分:Dijkstra vs Floyd

对比项 Dijkstra Floyd
解决问题 单源最短路径 多源最短路径
时间复杂度 O(n²)(朴素) O(n³)
空间复杂度 O(n) O(n²)
负权边 ❌ 不支持 ❌ 不支持(但可检测)
实现难度 中等 极简(5 行核心)
适用场景 求一点到其他所有点 求所有点对、求图的传递闭包

第四部分:完整测试代码

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <stdbool.h>

#define MAX_V 100
#define INF INT_MAX

typedef struct {
    int vertexNum;
    int edgeNum;
    int matrix[MAX_V][MAX_V];
} Graph;

void initGraph(Graph* g, int n) {
    g->vertexNum = n;
    g->edgeNum = 0;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            g->matrix[i][j] = (i == j) ? 0 : INF;
}

void addEdge(Graph* g, int u, int v, int w) {
    g->matrix[u][v] = w;
    g->matrix[v][u] = w;
    g->edgeNum++;
}

int findMinDist(int dist[], bool visited[], int n) {
    int min = INF, minIndex = -1;
    for (int i = 0; i < n; i++) {
        if (!visited[i] && dist[i] < min) {
            min = dist[i];
            minIndex = i;
        }
    }
    return minIndex;
}

void dijkstra(Graph* g, int start) {
    int dist[MAX_V], prev[MAX_V];
    bool visited[MAX_V];

    for (int i = 0; i < g->vertexNum; i++) {
        dist[i] = INF;
        prev[i] = -1;
        visited[i] = false;
    }
    dist[start] = 0;

    for (int count = 0; count < g->vertexNum; count++) {
        int u = findMinDist(dist, visited, g->vertexNum);
        if (u == -1) break;
        visited[u] = true;

        for (int v = 0; v < g->vertexNum; v++) {
            if (!visited[v] && g->matrix[u][v] != INF) {
                int newDist = dist[u] + g->matrix[u][v];
                if (newDist < dist[v]) {
                    dist[v] = newDist;
                    prev[v] = u;
                }
            }
        }
    }

    printf("===== Dijkstra(起点 %c)=====\n", 'A' + start);
    for (int i = 0; i < g->vertexNum; i++) {
        printf("%c: ", 'A' + i);
        if (dist[i] == INF) printf("不可达\n");
        else printf("距离=%d, 前驱=%c\n", dist[i], prev[i] == -1 ? '-' : ('A' + prev[i]));
    }
}

void floyd(Graph* g) {
    int n = g->vertexNum;
    int dist[MAX_V][MAX_V];

    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            dist[i][j] = g->matrix[i][j];

    for (int k = 0; k < n; k++)
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
                if (dist[i][k] != INF && dist[k][j] != INF)
                    if (dist[i][k] + dist[k][j] < dist[i][j])
                        dist[i][j] = dist[i][k] + dist[k][j];

    printf("\n===== Floyd 最短距离矩阵 =====\n   ");
    for (int i = 0; i < n; i++) printf("%4c", 'A' + i);
    printf("\n");
    for (int i = 0; i < n; i++) {
        printf("%c  ", 'A' + i);
        for (int j = 0; j < n; j++) {
            if (dist[i][j] == INF) printf("   ∞");
            else printf("%4d", dist[i][j]);
        }
        printf("\n");
    }
}

int main() {
    Graph g;
    initGraph(&g, 6);
    addEdge(&g, 0, 1, 5);  // A-B: 5
    addEdge(&g, 0, 3, 2);  // A-D: 2
    addEdge(&g, 1, 2, 3);  // B-C: 3
    addEdge(&g, 1, 4, 1);  // B-E: 1
    addEdge(&g, 2, 5, 4);  // C-F: 4
    addEdge(&g, 3, 4, 6);  // D-E: 6
    addEdge(&g, 4, 5, 2);  // E-F: 2

    dijkstra(&g, 0);
    floyd(&g);

    return 0;
}

总结

一、核心对比

对比项 Dijkstra Floyd
解决问题 一个起点到所有点 所有点对
思想 贪心 动态规划
时间复杂度 O(n²) O(n³)
负权边
代码量 30+ 行 5 行核心

二、一句话记忆

Dijkstra 用贪心每次选最近的未确定顶点更新邻居,求单源最短路径 O(n²)。Floyd 用三重循环枚举中转站,一次性求所有点对最短路径 O(n³)。两者都不支持负权边,有负权边需要用 Bellman-Ford。

相关推荐
fengxin_rou1 小时前
LeetCode链表经典五题:从相交到环形,双指针的妙用
算法·leetcode·链表
KaMeidebaby1 小时前
卡梅德生物技术快报|抗原如何自己检测?FAdV-4 重组抗原制备与 ELISA 体系技术调试指南
前端·人工智能·物联网·算法·百度
却道天凉_好个秋1 小时前
HEVC(二):如何实现并行处理
人工智能·算法·计算机视觉·hevc·瓦片技术·波前并行处理wpp
wayz111 小时前
Momentum:QQE(定量定性估计)技术指标详解
算法·金融·数据分析·量化交易·特征工程
Dontla1 小时前
聚类找不到簇原因分析(聚类失败)(DBSCAN聚类算法、eps参数、Epsilon参数、最大允许距离)
算法·数据挖掘·聚类
写代码写到手抽筋9 小时前
5G上行DCI字段判定:端口 流数 PMI选择详解
java·算法·5g
xieliyu.10 小时前
Java算法精讲:双指针(二)
java·开发语言·算法
wayz1110 小时前
Momentum:PSL(心理线指标)技术指标详解
算法·金融·数据分析·量化交易·特征工程
8Qi811 小时前
LeetCode 213:打家劫舍 II(House Robber II)—— 题解 ✅
算法·leetcode·职场和发展·动态规划