首页 > 编程语言 >最短路算法之 Dijkstra

最短路算法之 Dijkstra

时间:2022-09-03 23:00:30浏览次数:85  
标签:dist int 短路 Dijkstra 算法 maxn E2 E1 dis

部分内容参考了李煜东的《算法竞赛进阶指南》,在此声明。

  • 单源最短路径

    单源最短路径问题,是说,给定一张有向图(无向图)\(G=(V,E)\) ,\(V\) 是点集,\(E\) 是边集,\(|V|=n\),\(|E|=m\),节点是 \([1,n]\) 之间的连续整数,\((x,y,z)\) 描述一条从 \(x\) 到 \(y\) 边长为 \(z\) 的有向(无向)边,设 1 号点为起点,求长度为 \(n\) 的数组 \(dist\),其中 \(dist_i\) 表示从1到 \(i\) 的最短路径的长度。

  • Dijkstra 算法

    Dijkstra 算法可以求解不含有负边权单源最短路问题,其本质是贪心。
    下面来介绍它的算法实现流程。Dijkstra 算法将图中所有点分为2大类,下面不妨设为集合 \(E1\) 与集合 \(E2\)。其中 \(E1\) 表示的是已经确定最短路径的点,\(E2\) 表示的是还未确定最短路径的点。
    Dijkstra 的算法流程如下:

    1. 初始化 \(dist_1=0\),因为1号点是起点。
    2. 找出一个点 \(x \in E2\) 且 \(dist_x\) 最小,并将 \(x\) 加入集合 \(E1\)。
    3. 枚举 \(x\) 的所有出边 \((x,y,z)\) 更新 \(dist_y\) 的值,具体的,若 dist[y] > dist[x] + zdist[y] = dist[x] + z
    4. 重复执行2,3操作直至所有点均被加入 \(E1\) 集合。

    这里可能会有人产生疑惑,为什么我们一旦找出 \(x\) 就可以把它加入 \(E1\) 呢,这里就体现出了 Dijkstra 算法的核心与本质,贪心就用在这里。\(x\) 是 \(E2\) 中的点,也就是说 \(dist_x\) 还不确定,看起来好像不能将 \(x\) 加入 \(E1\) 集合,但仔细思考发现,能更新 \(dist_x\) 的点一定不在 \(E1\) 里,因为一旦某个点被加入了 \(E1\) ,那么在这之前,这个点一定会先更新它所能更新的点,所以能更新 \(dist_x\) 的点一定在 \(E2\) 中。其次,\(dist_x\) 是 \(E2\) 中最小的,又因为边权非负,所以 \(E2\) 中的点也无法更新 \(dist_x\) ,综上,\(dist_x\) 无法被任何一个点更新,所以 \(dist_x\) 不会更小,\(x\) 就可以顺利地加入集合 \(E1\) 了。

    下面我们分析一下这个算法的复杂度。
    根据上面的算法流程可知,在每一轮的循环中,必会有一个点被加入 \(E1\) 中(操作1),而循环终止条件是所有点均被加入集合 \(E1\),所以,我们最多循环 \(n\) 次。而每一次操作2的复杂度是 \(O(n)\),所以总复杂度就是 \(O(n^2)\)。

    \(O(n^2)\) 部分的代码,对应这道模板题

    点击查看代码
    #include <bits/stdc++.h>
    using namespace std;
    const int maxn = 510,inf = 0x7f7f7f7f;
    int n,m;
    int g[maxn][maxn],dis[maxn];
    bool vis[maxn];
    int dijkstra(){
        memset(dis,inf,sizeof(dis));
        dis[1] = 0;
        for(int i = 1;i <= n;i++){
            int mn = 0;
            for(int j = 1;j <= n;j++)
            if(!vis[j] && (!mn || dis[j] < dis[mn])) mn = j;
            vis[mn] = 1;
            for(int j = 1;j <= n;j++)
            if(!vis[j] && g[mn][j] != inf) dis[j] = min(dis[j],dis[mn] + g[mn][j]);
        }
        return dis[n] == inf ? -1 : dis[n];
    }
    int main(){
        scanf("%d%d",&n,&m);
        memset(g,inf,sizeof(g));
        int u,v,w;
        while(m--){
            scanf("%d%d%d",&u,&v,&w);
            g[u][v] = min(g[u][v],w);
        }
        printf("%d",dijkstra());
        return 0;
    }
    
    作者:旭日临窗
    链接:https://www.acwing.com/activity/content/code/content/3495042/
    来源:AcWing
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    

    但是在有些题中,点数和边数非常大,通常 \(n \le 10^5 , m \le 10^5\)。我们 \(O(n^2)\) 的代码无法通过,所以下面我们考虑优化。

    我们观察 \(O(n^2)\) 的代码,发现复杂度瓶颈在于内层循环,也就是操作2,所以我们重点考虑怎么优化操作2,也就是怎么快速找出 \(E2\) 中最小的 \(dist_x\)。在一个集合中找最小值,你想到了什么数据结构?没错,就是优先队列,或者说小堆根。具体的,每次当执行到操作3,并且 \(dist_y\) 可以被更新的时候,就将 \((y , dist_y)\) 这个二元组,加入优先队列,注意,优先队列默认是大堆根,所以我们可以用 pair <int,int> 来储存二元组,并且利用 pair 的内置比较函数,可以很方便的重载优先队列,将大堆根变成小堆根。这样操作2就变成了在优先队列的队头取出 \(x\) 和 \(dist_x\) 。复杂度可以降至 \(O(m \log n)\)。

    \(O(m \log n)\) 的代码,对应这道题

    点击查看代码
    #pragma GCC optimize(2)
    #include <bits/stdc++.h>
    #define PII pair <int,int>
    using namespace std;
    const int maxn = 2e5 + 10,inf = 0x7f7f7f7f;
    int n,m,cnt;
    int head[maxn],dis[maxn];
    bool vis[maxn];
    struct edge{int to,nxt,w;}e[maxn];
    void add(int u,int v,int w){e[cnt] = edge{v,head[u],w};head[u] = cnt++;}
    priority_queue <PII,vector <PII>,greater <PII> > q;
    int dijkstra(){
        memset(dis,0x7f,sizeof(dis));
        dis[1] = 0;
        q.push({0,1});
        while(!q.empty()){
            int u = q.top().second;q.pop();
            if(vis[u]) continue;
            vis[u] = 1;
            for(int i = head[u];~i;i = e[i].nxt){
                int v = e[i].to;
                if(dis[u] + e[i].w < dis[v]){
                    dis[v] = dis[u] + e[i].w;
                    q.push({dis[v],v});
                }
            }
        }
        return dis[n] == inf ? -1 : dis[n];
    }
    int main(){
        memset(head,-1,sizeof(head));
        scanf("%d%d",&n,&m);
        int u,v,w;
        while(m--){
            scanf("%d%d%d",&u,&v,&w);
            add(u,v,w);
        }
        printf("%d",dijkstra());
        return 0;
    }
    
    作者:旭日临窗
    链接:https://www.acwing.com/activity/content/code/content/3503934/
    来源:AcWing
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    

标签:dist,int,短路,Dijkstra,算法,maxn,E2,E1,dis
From: https://www.cnblogs.com/xrlc-home/p/16653909.html

相关文章

  • 算法总结
    1.展平二叉搜索树给你一棵二叉搜索树,请 按中序遍历将其重新排列为一棵递增顺序搜索树,使树中最左边的节点成为树的根节点,并且每个节点没有左子节点,只有一个右子节点。题......
  • 我的第一本算法书 第二三四章
    第2章排序2.1什么是排序将输入的数字按照从小到大的顺序进行排列2.2冒泡排序从右开始,两两比较.逐渐将最小值移动到最左侧再从最左侧逐步往左移动,直至所有数......
  • 算法-小红书2020校招前端笔试题卷三
     薯队长写了一篇笔记草稿,请你帮忙输出最后内容。 1.输入字符包括,"("    ,    ")"    和    "<"和其他字符。 2.其他字符表示笔记内容。 3.()之间......
  • YOLO算法的学习
            YOLOV1置信度(confidence):当前点所对应的候选框,是否是物体。√网络架构   注:7*7*30:7*7的格子,每个格子当中有30个值,每个格子产生两种候......
  • 洗牌算法学习笔记
    洗牌算法QuadvsPlaneQuad:两个单位三角形组成的四边形PlanePlane是一个10行10列一共200个单位三角形组成的三角形越多也就意味着模型顶点越多,所制作的......
  • 机器学习中的数值查找算法(5)——字符串查找算法(Boyer-Moore算法)
    原文链接:机器学习中的数值查找算法(5)——字符串查找算法(Boyer-Moore算法)–每天进步一点点(longkui.site)Boyer—Moore算法简称BM算法,它是在字符串查找的方法中桐KMP......
  • 献芹奏曝-Python面试题-算法-链表篇
    上一篇:献芹奏曝-Python面试题    开篇的话:本文目的是收集和归纳力扣上的算法题,希望用python语言,竭我所能做到思路最清奇、代码最简洁、方法最广泛、性能最高效,了解......
  • 排序算法整理(包括初赛)
    排序算法整理常见考点将一个乱掉的字符串排回有序(以交换为基本操作)的最少操作,就是冒泡排序。排序算法的稳定性排序算法的时间复杂度排序算法的稳定性稳定性是指排......
  • 最短路
    单源最短路:Dijkstra没有堆优化的是\(O(n^2)\)的。但是由于防止一些毒瘤完全图,在此一并表出。(其实好像也卡不了多少)dijkstra的思想其实就是贪心。每次扫一遍所有点然后......
  • python | 算法大神左神(左程云)算法课程 二叉树部分【中】
    1.二叉树宽度......