一文彻底搞懂Dijkstra算法(迪杰斯特拉算法)

Dijkstra算法(迪杰斯特拉算法)用于在给定图中查找某个顶点到其它顶点的最短路径。

要想彻底理解 Dijkstra 算法,读者首先要搞清楚什么是最短路径。

最短路径的含义

对于逻辑关系为“多对多”的数据集,数据结构中推荐用图结构来存储。

在给定的一张图中,一个顶点到另一个顶点的路径可能有多条,最短路径指的就是顶点之间“最短”的路径。

举个简单的例子:

上图不仅是一张图,顶点之间的边还带有权值,比如 V1 到 V2 这条边的权值为 5,因此更确切的说,这是一张网,而且是有向网。

在不同的场景中,路径“最短”的含义也有所差异,比如途径顶点数量最少、总权值最小等。提到最短路径,往往指的是总权值最小的路径,所以常常在网结构(带权的图)中讨论最短路径问题,包括有向网和无向网。

关于图结构的定义和分类,不清楚的读者可以看一篇文章:图存储结构,数据结构中的图从本节开始,我将为大家介绍最后一种存储结构,也是数据结构中最复杂、难掌握的存储结构 图 。 图结构常用来存储逻辑关系为多对多的数据。比如说,一个学生可以同时选择多门课icon-default.png?t=O83Ahttps://xiexuewu.github.io/view/338.html

观察上图中的这张网,从 V0 到 V5 的路径有多条,包括:

  • V0 -> V5,总权值为 100
  • V0 -> V4 -> V5,总权值为 30+60 = 90
  • V0 -> V4 -> V3 -> V5,总权值为 30+20+10 = 60
  • V0 -> V2 -> V3 -> V5,总权值为 10+50+10 = 70

通过比较这些路径的总权值,发现第 3 条路径的权值最短,因为 V0 -> V4 -> V3 -> V5 就是从 V0 到 V5 的最短路径。

现如今,大家出行再也不用担心找不到路了,车上有车载导航,手机上也可以安装各种导航 App,只要输入目的地,导航会自动帮我们规划一条距离最短的路线,这是最短路径在实际生活中的典型应用之一。

在指定的一张图或者网中查找最短路径,常用的解决方案有两个,分别是 Dijkstra 算法和 Floyd(弗洛伊德)算法。本节我重点讲解 Dijkstra 算法,Floyd 算法会单独写一篇文章介绍。

Dijkstra算法

在给定的带权图(网)中,Dijkstra算法(迪杰斯特拉算法)可以找到从某个顶点到其它所有顶点的最短路径。

Dijkstra算法既适用于有向带权图,也适用于无向带权图。

需要注意的是,用Dijkstra算法查找最短路径时,必须保证图中所有边(弧)的权值为非负数,否则会导致查找失败。

首先通过一个实例,给大家展示 Dijkstra 算法查找最短路径的过程。

图 1 有向带权图

在图 1 中查找 V0 到其它顶点的最短路径,Dijkstra算法的整个查找过程是:
1) 统计从 V0 到其它顶点的权值,如下表所示: 

表 2 V0->V2 是最短路径

∞ 表示 V0 到目标顶点之间不存在弧,它们之间的距离为无穷大。

表格中 V0->V2 的权值最小,可以断定 V0 到 V2 的最短路径就是 V0->V2。

断定 V0-V2 是最短路径的依据是:

假设存在一条比 V0->V2 权值更小的路径,比如用 V0->Vj -> ... ->V2 表示,那么 V0->Vj 的权值一定比 V0->V2 小,表格中权值最小的路径就应该选择 V0->Vj 而不是 V0->V2,但显然这种假设是不成立的。

后续断定某个路径为最短路径时,也可以采用此方法验证。

2) 找到 V0->V2 最短路径后,继续从 V2 出发,统计从 V0 经过 V2 再到达其它顶点的路径权值,如果比表 2 记录的权值更小,表明找到了一条更短的路径,将此路径信息更新到表格中。

表 3 找到了一条从 V0 到 V3 更短的路径

从 V0->V2 继续出发,可以找到 V0->V2->V3 路径的权值比先前记录的 V0->V3 更小,因此将 V0->V2->V3 这条路径的信息更新到表中。

除了已知的最短路径外,表 3 中 V0->V4 的权值最小,所以 V0 到 V4 的最短路径就是 V0->V4。

表 4 V0->V4 是最短路径

3) 找到 V0->V4 最短路径后,继续从 V4 出发,统计从 V0 经过 V4 再到达其它顶点的路径权值,如果比表 4 记录的权值更小,表明找到了一条更短的路径,将此路径信息更新到表格中。

表 5 找到了两条更短的路径

从 V0->V4 出发,可以找到一条比 V0->V5 更短的路径 V0->V4->V5,还可以找到一条比 V0->V2->V3 更短的路径 V0->V4->V5。

除了已知的最短路径之外,表 5 中 V0->V4->V3 的权值最小,所以 V0 到 V3 的最短路径就是 V0->V4->V3。

表 6 V0->V4->V3 是最短路径

4) 找到 V0->V4->V3 最短路径后,继续从 V3 出发,统计从 V0 经过 V4 和 V3 再到达其它顶点的权值,如果比表 6 记录的权值更小,表明找到了一条更短的路径,将此路径信息更新到表格中。

从 V0->V4->V3 出发,可以找到一条比 V0->V4->V5 更短的路径 V0->V4->V3->V5,将此路径信息更新到表格中。

表 7 找到了一条从 V0 到 V5 更短的路径

除了已知的最短路径外,表 7 中 V0->V4->V3->V5 的权值最小,因此断定 V0 到 V5 的最短路径就是 V0->V4->V3->V5。

表 8 V0->V4->V3->V5 是最短路径

5) 从图 1 中可以看到,V5 顶点的出度为 0,因此无法从 V5 出发找到前往其它顶点的路径,整个算法执行结束。表 8 中记录的路径就是从 V0 到各个顶点的最短路径,其中 V0 无法达到 V1,因此路径的权值用 ∞ 来表示。

Dijkstra算法的具体实现

实现 Dijkstra 算法,可以参考如下的 C 语言程序:

#include <stdio.h>
#define MAX_VERtEX_NUM 20                   //顶点的最大个数
#define VRType int                          //表示弧的权值的类型
#define VertexType int                      //图中顶点的数据类型
#define INFINITY 65535

typedef struct {
    VertexType vexs[MAX_VERtEX_NUM];               //存储图中顶点数据
    VRType arcs[MAX_VERtEX_NUM][MAX_VERtEX_NUM];   //二维数组,记录顶点之间的关系
    int vexnum, arcnum;                            //记录图的顶点数和弧(边)数
}MGraph;

typedef int PathMatrix[MAX_VERtEX_NUM];      //此类型数组用来存储最短路径中经过的顶点的下标
typedef int ShortPathTable[MAX_VERtEX_NUM];  //此类型数组用来存储各个最短路径的权值和

//根据顶点本身数据,判断出顶点在二维数组中的位置
int LocateVex(MGraph* G, VertexType v) {
    int i = 0;
    //遍历一维数组,找到变量v
    for (; i < G->vexnum; i++) {
        if (G->vexs[i] == v) {
            break;
        }
    }
    //如果找不到,输出提示语句,返回-1
    if (i > G->vexnum) {
        printf("no such vertex.\n");
        return -1;
    }
    return i;
}

//构造有向网
void CreateUDG(MGraph* G) {
    int i, j, v1, v2, w, n, m;
    scanf("%d,%d", &(G->vexnum), &(G->arcnum));
    for (i = 0; i < G->vexnum; i++) {
        scanf("%d", &(G->vexs[i]));
    }
    for (i = 0; i < G->vexnum; i++) {
        for (j = 0; j < G->vexnum; j++) {
            G->arcs[i][j] = INFINITY;
        }
    }
    for (i = 0; i < G->arcnum; i++) {
        scanf("%d,%d,%d", &v1, &v2, &w);
        n = LocateVex(G, v1);
        m = LocateVex(G, v2);
        if (m == -1 || n == -1) {
            printf("no this vertex\n");
            return;
        }
        G->arcs[n][m] = w;
    }
}

//迪杰斯特拉算法,v0表示有向网中起始点所在数组中的下标
void ShortestPath_Dijkstra(MGraph G, int v0, PathMatrix P, ShortPathTable D) {
    int final[MAX_VERtEX_NUM] = { 0 };//用于存储各顶点是否已经确定最短路径的数组
    int k = 0, v, i, w;
    //对各数组进行初始化
    for (v = 0; v < G.vexnum; v++) {
        D[v] = G.arcs[v0][v];
    }
    //由于下标v0的顶点为起始点,所以不用再判断
    D[v0] = 0;
    final[v0] = 1;
    for (i = 0; i < G.vexnum; i++) {
        int min = INFINITY;
        //选择到各顶点权值最小的顶点,即为本次能确定最短路径的顶点
        for (w = 0; w < G.vexnum; w++) {
            if (!final[w]) {
                if (D[w] < min) {
                    k = w;
                    min = D[w];
                }
            }
        }
        //设置该顶点的标志位为1,避免下次重复判断
        final[k] = 1;
        //对v0到各顶点的权值进行更新
        for (w = 0; w < G.vexnum; w++) {
            if (!final[w] && (min + G.arcs[k][w] < D[w])) {
                D[w] = min + G.arcs[k][w];
                P[w] = k;//如果<k,w>是组成最短路径的部分,只需要将 k 存储在 P 数组相应位置即可
            }
        }
    }
}

//递归输出顶点之间最短路径的具体线路
void printPath(PathMatrix P, int i)
{
    int k = P[i];
    if (k == 0)
        return;
    printPath(P,k);
    printf("V%d-", k);
}

// 输出 v0 和各个顶点之间的最短路径
void printMatrix(MGraph G, int v0, PathMatrix P, ShortPathTable D) {
    int i, j;
    for (i = 0; i < G.vexnum; i++) {
        if (i == v0) {
            continue;
        }
        printf("V%d-V%d: 最短路径为:", v0, i);
        if (D[i]== INFINITY)
            printf("%s\n", "INF");
        else {
            printf("%d", D[i]);
            printf(",依次经过:V%d-", v0);
            //调用递归函数
            printPath(P, i);
            printf("V%d\n", i);
        }
    }
}

int main() {
    PathMatrix P = { 0 };
    ShortPathTable D = { 0 };
    MGraph G;
    CreateUDG(&G);
    ShortestPath_Dijkstra(G, 0, P, D);
    printMatrix(G, 0, P, D);
    return 0;
}

程序中,存储有向带权图采用的是顺序存储结构。

存储某个顶点到其它顶点的最短路径,用一维数组就可以实现。以表 8 中 V0->V4->V3->V5 这条从 V0 到 V5 的最短路径为例,它其实就是 V0->V4->V3 和 V3->V5 的组合,而前者是 V0->V3 的最短路径,可以看做是 V0->V4 和 V4->V3 的组合。程序中,存储最短路径的一维数组(名称为 P)为:

图 9 存储最短路径的一维数组

下标 0~5 对应的分别是 V0~V5,根据数组中存储的数据,查找 V0->V5 最短路径的过程是:

  • 下标 5 的位置存储的数字是 3,表明 V3->V5 是 V0->V5 最短路径的组成部分,继续查找 V0->V3 的最短路径;
  • 下标 3 的位置存储的数字是 4,表明 V4->V3 是 V0->V5 最短路径的组成部分,继续查找 V0->V4 的最短路径;
  • 下标 4 的位置存储的数组是 0,表明 V0->V4 是 V0->V5 最短路径的组成部分。

因此 V0->V5 的最短路径就是 V0->V4->V3->V5。

将图 1 中的有向网输入到程序中,运行结果为:

6,8
0
1
2
3
4
5
0,5,100
0,4,30
0,2,10
1,2,5
2,3,50
3,5,10
4,3,20
4,5,60
V0-V1: 最短路径为:INF
V0-V2: 最短路径为:10,依次经过:V0-V2
V0-V3: 最短路径为:50,依次经过:V0-V4-V3
V0-V4: 最短路径为:30,依次经过:V0-V4
V0-V5: 最短路径为:60,依次经过:V0-V4-V3-V5

总结

Dijkstra算法(迪杰斯特拉算法)可以在给定网(带权图)中查找一个顶点到其它顶点的最短路径,算法的时间复杂度为 O(n^2)

如果需要查找网中任意两个顶点之间的最短路径,虽然Dijkstra算法也能解决(对每个顶点都执行一次Dijkstra算法),但更建议使用:Floyd弗洛伊德算法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值