[算法系列之三十]Dijkstra单源最短路径算法

简介:

单源最短路径问题

给定一个带权有向图 G=(V,E) ,其中每条边的权是一个非负实数。另外,还给定 V 中的一个顶点,称为源。现在我们要计算从源到所有其他各顶点的最短路径长度。这里的长度是指路上各边权之和。这个问题通常称为单源最短路径问题。

前面Bellman-Ford最短路径算法讲了单源最短路径的Bellman-Ford算法(动态规划算法)。这里介绍另外一个更常见的算法Dijkstra算法。

Dijkstra算法和 最小生成树Prim算法最小生成树算法非常类似,大家可以先熟悉下个算法。两个算法都是基于贪心算法。虽然Dijkstra算法相对来说比Bellman-Ford 算法更快,但是不适用于有负权值边的图,贪心算法决定了它的目光短浅。而Bellman-Ford 算法从全局考虑,可以检测到有负权值的回路。

这里模仿MST(Minimum Spanning Tree)的Prim算法,我们创建一个SPT(最短路径树),最初只包含源点。我们维护两个集合,一组已经包含在SPT(最短路径树)中的顶点S集合,另一组是未包含在SPT内的顶点T集合。每次从T集合中选择到S集合路径最短的那个点,并加入到集合S中,并把这个点从集合T删除。直到T集合为空为止。

T集合用最小优先队列Q存储,该队列包含所有属于V-S的节点(这些节点尚未确定最短路径的权),且以d值为关键字排列各节点。

初始时,Q包含了除源点src以外的其他节点,这些节点的d值为无穷大。源点src进入S之后,d[src]=0。算法反复从Q中取出d值最小的节点u,u属于V-S,把u插入集合S中,并对u的所有出边进行松弛,这一过程直到Q队列为空为止。

举例

如下图所示的图:

这里写图片描述

S集合最初为空,然后选取源点0,S集合为 {0},源点到其它所有点的距离为 {0, INF, INF, INF, INF, INF, INF, INF} 。图中蓝色表示 SPT,迭代的过程如下:

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

最终得到 SPT(最短路径树) 如下:

这里写图片描述

注意点

  • The code calculates shortest distance, but doesn’t calculate the path information. We can create a parent array, update the parent array when distance is updated (like prim’s implementation) and use it show the shortest path from source to different vertices.

  • The code is for undirected graph, same dijekstra function can be used for directed graphs also.

  • The code finds shortest distances from source to all vertices. If we are interested only in shortest distance from source to a single target, we can break the for loop when the picked minimum distance vertex is equal to target (Step 3.a of algorithm).

  • Time Complexity of the implementation is O(V^2). If the input graph is represented using adjacency list, it can be reduced to O(E log V) with the help of binary heap. We will soon be discussing O(E Log V) algorithm as a separate post.

  • Dijkstra’s algorithm doesn’t work for graphs with negative weight edges. For graphs with negative weight edges, Bellman–Ford algorithm can be used, we will soon be discussing it as a separate post.

时间复杂度分析

算法的执行速度取决于优先队列Q的数据结构。有三种数据结构可供选择。

  • 用一维数组实现优先队列Q。在该算法中,每次都是从最小优先队列Q中取出d值最小的节点需要的时间为O(V),存在|V|次这样的操作,所以最小优先队列Q取出d值最小的节点的全部运行时间为O(V^2)。因为每个节点v属于V仅被插入集合S一次,所以在算法的执行过程中,v的每条邻接边在for循环中仅被考察一次。因为在所有邻接边的总数为|E|,所以在该for循环中总共存在|E|次迭代,每次迭代运行时间为O(1),导致整个算法的运行时间为O(V^2+E)=o(V^2) 。显然对于规模不大的稠密图,可次啊用数组实现优先队列。
  • 用二叉堆实现优先队列Q。在该算法中,每次从最小优先队列Q取出d值最小的节点需要的时间为O(lnV),存在|V|次这样的操作。建立二叉堆需要的时间为O(V)。在relax过程中的赋值语句d[v] <- d[u]+w(u,v)是通过调整节点v在二叉堆的位置来完成的,运行时间为O(lnV),并且至多存在|E|次这样的操作。因此算法的时间复杂度O((V+E)lnV)。通常情况下,边数|E|都不小于节点数|V|,所以运行时间可以简化为O(ElnV)。显然在稀疏图的情形下用二叉堆来实现优先队列是比较实用。
  • 用Fibonacci堆实现优先队列。运行时间为O(VlnV+E)。

代码

/*-------------------------------------
*   日期:2015-04-23
*   作者:SJF0115
*   题目: Dijkstra算法(单源最短路径)
*   博客:
------------------------------------*/
#include <iostream>
#include <climits>
#include <vector>
using namespace std;


//从未包含在SPT的集合T中,选取一个到S集合的最短距离的顶点
int GetMinVertex(int dist[], bool visited[],int v) {
    int min = INT_MAX;
    int index;
    for(int i = 0;i < v;++i){
        // 没访问过且距SPT最短的顶点
        if(!visited[i] && dist[i] < min){
            min = dist[i];
            index = i;
        }//if
    }//for
    return index;
}

// 打印结果
void Print(int dist[],int n){
    for(int i = 0;i < n;++i){
        cout<<"距离顶点"<<i<<"最短距离->"<<dist[i]<<endl;
    }//for
}

//source 代表源点
void Dijkstra(vector<vector<int> > graph,int src) {
    // 顶点个数
    int v = graph.size();
    // dist[i]表示从源点到顶点i的距离
    int dist[v];
    // visited[i]=true 如果顶点i包含在SPT中
    bool visited[v];
    // 初始化 0代表不可达
    for(int i = 0;i < v;++i){
        dist[i] = (graph[src][i] == 0 ? INT_MAX:graph[src][i]);
        visited[i] = false;
    }//for
    dist[src] = 0;
    visited[src] = true;

    // 迭代V-1次,因此不用计算源点了,还剩下V-1个需要计算的顶点。
    for(int i = 1;i < v;++i){
        // T集合中到S集合距离最短的顶点
        int u = GetMinVertex(dist,visited,v);
        // 加入SPT中
        visited[u] = true;
        // 更新T集合中顶点到S集合顶点的距离
        for (int j = 0;j < v;++j){
            if (!visited[j] && graph[u][j] && dist[u] != INT_MAX
                    && dist[u] + graph[u][j] < dist[j]){
                dist[j] = dist[u] + graph[u][j];
            }//if
        }//for
    }//for
    // 打印结果
    Print(dist,v);
}

int main() {
    vector<vector<int> > graph =
        {
            {0, 4, 0, 0, 0, 0, 0, 8, 0 },
            {4, 0, 8, 0, 0, 0, 0, 11, 0 },
            {0, 8, 0, 7, 0, 4, 0, 0, 2 },
            { 0, 0, 7, 0, 9, 14, 0, 0, 0 },
            { 0, 0, 0, 9, 0, 10, 0, 0, 0 },
            { 0, 0, 4, 0, 10, 0, 2, 0, 0 },
            { 0, 0, 0, 14, 0, 2, 0, 1, 6 },
            { 8, 11, 0, 0, 0, 0, 1, 0, 7 },
            { 0, 0, 2, 0, 0, 0, 6, 7, 0 }
        };
    // 单源最短路径
    Dijkstra(graph,0);
    return 0;
}
AI 代码解读

转载于:Dijkstra最短路径算法[贪心]

目录
打赏
0
0
0
0
63
分享
相关文章
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
178 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
【狂热算法篇】探秘图论之 Floyd 算法:解锁最短路径的神秘密码(通俗易懂版)
【狂热算法篇】探秘图论之 Floyd 算法:解锁最短路径的神秘密码(通俗易懂版)
【狂热算法篇】探秘图论之Dijkstra 算法:穿越图的迷宫的最短路径力量(通俗易懂版)
【狂热算法篇】探秘图论之Dijkstra 算法:穿越图的迷宫的最短路径力量(通俗易懂版)
路径规划算法 - 求解最短路径 - A*(A-Star)算法
路径规划算法 - 求解最短路径 - A*(A-Star)算法
296 1
算法设计(动态规划应用实验报告)实现基于贪婪技术思想的Prim算法、Dijkstra算法
这篇文章介绍了基于贪婪技术思想的Prim算法和Dijkstra算法,包括它们的伪代码描述、Java源代码实现、时间效率分析,并展示了算法的测试用例结果,使读者对贪婪技术及其应用有了更深入的理解。
算法设计(动态规划应用实验报告)实现基于贪婪技术思想的Prim算法、Dijkstra算法
HanLP — HMM隐马尔可夫模型 - 路径规划算法 - 求解最短路径 - 维特比(Viterbi)算法
HanLP — HMM隐马尔可夫模型 - 路径规划算法 - 求解最短路径 - 维特比(Viterbi)算法
97 0
HanLP — HMM隐马尔可夫模型 - 路径规划算法 - 求解最短路径 - 维特比(Viterbi)算法
|
9月前
|
Java语言实现最短路径算法(Shortest Path)
Java语言实现最短路径算法(Shortest Path)
108 3
|
8月前
|
路径规划算法 - 求解最短路径 - Dijkstra(迪杰斯特拉)算法
路径规划算法 - 求解最短路径 - Dijkstra(迪杰斯特拉)算法
167 0
基于Dijkstra算法的最优行驶路线搜索matlab仿真,以实际城市复杂路线为例进行测试
使用MATLAB2022a实现的Dijkstra算法在城市地图上搜索最优行驶路线的仿真。用户通过鼠标点击设定起点和终点,算法规划路径并显示长度。测试显示,尽管在某些复杂情况下计算路径可能与实际有偏差,但多数场景下Dijkstra算法能找到接近最短路径。核心代码包括图的显示、用户交互及Dijkstra算法实现。算法基于图论,不断更新未访问节点的最短路径。测试结果证明其在简单路线及多数复杂城市路况下表现良好,但在交通拥堵等特殊情况下需结合其他数据提升准确性。

热门文章

最新文章