当前位置: 首页 > news >正文

Dijkstra——通过不断松弛来解决单源最短路径问题的算法

文章目录

  • 前言
  • Dijkstra思路
  • 图解实例
  • 代码实现
  • 总结

前言

Dijkstra算法又称迪杰斯特拉算法,它采用的是一种贪心的策略,使用广度优先搜索的方式解决带权有向图或者无向图的单源最短路径问题,需要注意的是它不能处理带负边权的情况,核心思想就是“守住现有阵地不断攻占底盘”,这一点可以在后面代码实现中慢慢体会,接下来梳理一下算法思路。

Dijkstra思路

Dijkstra 算法的思路是维护一个点集合 S 和一个用来保存起点 m 到各个顶点到各个顶点最短距离的数组 dis,用邻接数组来表示带权图信息。

初始情况时,集合 S 中只包括起点m,通过图信息来初始化 dis 数组,将起点 m 可以直接到达的点设置为边的权值,不能到达的点设置为无穷大,比如点 m 到点 n 的距离是d,则 dis[n] = d

然后从带权图中选择不在集合S 中的到点 m 距离最近的点,假设为 n,把它加到集合 S 中,然后尝试通过点 n “松弛” 那些不在集合 S 中的点到点 m的距离,更新 dis 数组信息,具体操作就是使用点 n 作为中转,如果距离如果点 m 到任意点 x 通过点 n 中转距离变短了,那么就更新 dis[x] 的值。

之后不断重复上面的“松弛”操作,直到集合 S 中包含了所有得到顶点,至此就通过Dijkstra算法求解出了从点 m 到图中任意点的最短距离。

图解实例

看了上面的关于Dijkstra算法的文字描述可能还是有点蒙,这时候需要画个图来解释一下,对于算法问题,特别是图论方面的算法题,有时候真的是一图胜千言,奈何我真的是不想画图,一方面因为“懒”,另一方面就是图片的搬运比较麻烦,所以对于大部分问题我都是文字描述,但是为了解释这个Dijkstra我还是决定画一画,假如求解从点a 到各个顶点的最短距离,初始图信息如下:

Dijkstra_0

第一步,我们把点 a 添加到集合 S 中变为 S = {a},然后初始化dis数组为 dis = {0, 1, 12, ∞, ∞, ∞},加入集合的点用红色表示,操作之后更新如下:

Dijkstra_1

第二步,找到距离点 a 最近的且不在 S 中的点,根据 dis 数组计算应该是点 b,将点 b 添加到集合 S 中,通过点 b 中转更新 dis 数组,dis[c]变为8,dis[d]变为4,更新后集合为 S = {a, b}, 距离数组为 dis = {0, 1, 8, 4, ∞, ∞}, 图信息如下:

Dijkstra_2

第三步,找到距离点 a 最近的且不在 S 中的点,根据 dis 数组计算应该是点 d,将点 d 添加到集合 S 中,通过点 d 中转更新 dis 数组,dis[e]变为14,dis[f]变为17,更新后集合为 S = {a, b, d}, 距离数组为 dis = {0, 1, 8, 4, 14, 17}, 图信息如下:

Dijkstra_3

第四步,找到距离点 a 最近的且不在 S 中的点,根据 dis 数组计算应该是点 c,将点 c 添加到集合 S 中,通过点 c 中转更新 dis 数组,dis[e]变为13,更新后集合为 S = {a, b, d, c}, 距离数组为 dis = {0, 1, 8, 4, 13, 17}, 图信息如下:

Dijkstra_4

第五步,找到距离点 a 最近的且不在 S 中的点,根据 dis 数组计算应该是点 e,将点 e 添加到集合 S 中,通过点 e 中转更新 dis 数组,通过距离判断发现此次不需要更新dis数组,更新后集合为 S = {a, b, d, c, e}, 距离数组为 dis = {0, 1, 8, 4, 13, 17}, 图信息如下:

Dijkstra_5

第六步,找到距离点 a 最近的且不在 S 中的点,根据 dis 数组计算应该是点 f,将点 f 添加到集合 S 中,至此集合 S 中包含了所有的顶点,Dijkstra算法执行结束,集合信息为 S = {a, b, d, c, e, f}, 距离数组为 dis = {0, 1, 8, 4, 13, 17}, 图信息如下:

Dijkstra_6

代码实现

通过上面的图解实例对于Dijkstra的实现应该有了一些思路,那么接下来我们把它转化成代码:

void Dijkstra(vector<vector<int>>& graph)
{
    vector<int> dis = graph[0];
    set<int> S;

    const int n = dis.size();
    for (int i = 0, x = 0; i < n; i++, x = 0) {

        // find  minimum weight
        for (int j = 0; j < n; j++) if (!S.count(j) && (x == 0 || dis[j] < dis[x])) x = j;

        S.insert(x);

        // relax
        for (int j = 0; j < n; j++) if (!S.count(j) && dis[x] + graph[x][j] < dis[j]) dis[j] = dis[x] + graph[x][j];
    }
}

运行上述代码之后我们便得到了节点0到任意点的最短路径长度数组 dis

从上面的分析我们可以知道从点 a 到点 f 的最短路径长度是 17,那么最短路径怎样求呢?

其实只要在做松弛操作时记录每个节点是从哪个节点松弛得到的就可以了,比如可以使用一个pre数组来记录这个信息,当计算 dis 结束时通过pre数组反推就可以得到最短路径,简单实现如下:

void Dijkstra(vector<vector<int>>& graph)
{
    vector<int> dis = graph[0];
    set<int> S;

    const int n = dis.size();
    vector<int> pre(n, 0); // save previous point index

    for (int i = 0, x = 0; i < n; i++, x = 0) {

        // find  minimum weight
        for (int j = 0; j < n; j++) if (!S.count(j) && (x == 0 || dis[j] < dis[x])) x = j;

        S.insert(x);

        // relax
        for (int j = 0; j < n; j++) if (!S.count(j) && dis[x] + graph[x][j] < dis[j]) {
            dis[j] = dis[x] + graph[x][j];
            pre[j] = x;
        }
    }

    // output path info
    vector<int> path{5};
    while(path.back() != 0) {
        path.push_back(pre[path.back()]);
    }

    for (auto it = path.rbegin(); it != path.rend(); it++)
        cout << *it << " ";
}

总结

  • Dijkstra算法的时间复杂度为O(N^2),空间复杂度为 O(N),如果对时间复杂度有更高要求可以使用堆结构进行优化
  • Dijkstra是一种求解单源最短路径的算法,在时间复杂度这一项要优于之前所说的 Floyd 算法
  • Dijkstra不能处理带负边权的情况,不过实际生活中类似于行车路线、管道铺设等问题都不会有负边权,应用还是比较广泛的
  • 该算法仔细分析之后还是比较好理解的,不过还是有一些变型和编程技巧,需要在实际问题中灵活变通

==>> 反爬链接,请勿点击,原地爆炸,概不负责!<<==

历史总是惊人的相似,却不会简单的重复。在柯立芝实行了以放任自流的经济政策之后,紧接着便迎来了1929年的大萧条;而在克林顿到小布什任期内采取的经济自由化的政策,引发了之后2008年的国际金融危机;如今我们抬头看看大洋彼岸那疯狂运转的印钞机,这次的泡泡或许很快就能迎来炸裂的时刻~

相关文章:

  • C++11中的std::atomic保证的原子性是什么
  • .bat批处理(十):从路径字符串中截取盘符、文件名、后缀名等信息
  • linux环境下从路径字符串中截取目录和文件名信息
  • MD5是用来加密的吗?BCrypt又是什么呢
  • 树的带权路径长度和哈夫曼树
  • 完全图与强连通图的那些坑
  • linux环境下恢复rm误删的文件
  • 记一次使用Valgrind查找解决内存问题的玄幻旅程
  • 网络工具nc的常见功能和用法
  • git常用配置——git show/diff tab 显示宽度
  • Windows设置防火墙允许指定应用正常使用网络
  • 2021年终总结——脚踏实地,为下一次腾飞积蓄力量
  • 通过WindowsStore安装QuickLook小工具方便文件预览
  • linux环境下随时照看服务器进程的ps和top命令
  • 简单梳理下git的使用感受,思考git中最重要的是什么
  • 【附node操作实例】redis简明入门系列—字符串类型
  • 08.Android之View事件问题
  • bearychat的java client
  • Bytom交易说明(账户管理模式)
  • css属性的继承、初识值、计算值、当前值、应用值
  • dva中组件的懒加载
  • eclipse(luna)创建web工程
  • ESLint简单操作
  • Fastjson的基本使用方法大全
  • iOS动画编程-View动画[ 1 ] 基础View动画
  • Linux gpio口使用方法
  • SQLServer之索引简介
  • underscore源码剖析之整体架构
  • vue总结
  • 关于Android中设置闹钟的相对比较完善的解决方案
  • 开放才能进步!Angular和Wijmo一起走过的日子
  • 微信支付JSAPI,实测!终极方案
  • 异步
  • 用quicker-worker.js轻松跑一个大数据遍历
  • 智能网联汽车信息安全
  • 完善智慧办公建设,小熊U租获京东数千万元A+轮融资 ...
  • ​ 轻量应用服务器:亚马逊云科技打造全球领先的云计算解决方案
  • #{}和${}的区别?
  • #laravel 通过手动安装依赖PHPExcel#
  • (12)Hive调优——count distinct去重优化
  • (ibm)Java 语言的 XPath API
  • (Oracle)SQL优化技巧(一):分页查询
  • (二)丶RabbitMQ的六大核心
  • (篇九)MySQL常用内置函数
  • (循环依赖问题)学习spring的第九天
  • (最简单,详细,直接上手)uniapp/vue中英文多语言切换
  • .NET “底层”异步编程模式——异步编程模型(Asynchronous Programming Model,APM)...
  • .net core 6 集成 elasticsearch 并 使用分词器
  • @Autowired和@Resource装配
  • @LoadBalanced 和 @RefreshScope 同时使用,负载均衡失效分析
  • @vue/cli脚手架
  • [2013][note]通过石墨烯调谐用于开关、传感的动态可重构Fano超——
  • [Angularjs]asp.net mvc+angularjs+web api单页应用之CRUD操作
  • [C# 开发技巧]实现属于自己的截图工具
  • [CareerCup] 6.1 Find Heavy Bottle 寻找重瓶子