Catalog
  1. 1. 前言
  2. 2. 什么是dijkstra?
  3. 3. dijkstra的原理/流程?
    1. 3.0.1. dijkstra的流程如下:
  • 4. dijkstra为什么是正确的
    1. 4.0.1. 图解
  • 5. 为什么dijkstra不能处理有负权边的情况?
  • 6. dijkstra的堆优化?
  • Dijkstra-学习

    前言

    SPFA算法由于它上限O(NM)的时间复杂度,被卡掉的几率很大.在算法竞赛中,我们需要一个更稳定的算法:dijkstra

    什么是dijkstra?

    dijkstra是一种单源最短路径算法,时间复杂度上限为O(n^2)(朴素),在实际应用中较为稳定;加上堆优化之后更是具有O((n+m)log^2 n)的时间复杂度,在稠密图中有不俗的表现.

    dijkstra的原理/流程?

    dijkstra本质上的思想是贪心,它只适用于不含负权边的图.
    我们把点分成两类,一类是已经确定最短路径的点,称为”白点”,另一类是未确定最短路径的点,称为”蓝点”

    dijkstra的流程如下:

    1. 初始化dis[start] = 0,其余节点的dis值为无穷大.
    2. 找一个dis值最小的蓝点x,把节点x变成白点.
    3. 遍历xx的所有出边(x,y,z),若dis[y] > dis[x] + z,则令dis[y] = dis[x] + z
    4. 重复2,3两步,直到所有点都成为白点.

      dijkstra为什么是正确的

      当所有边长都是非负数的时候,全局最小值不可能再被其他节点更新.所以在第2步中找出的蓝点x必然满足:dis[x]已经是起点到x的最短路径.我们不断选择全局最小值进行标记和拓展,最终可以得到起点到每个节点的最短路径的长度

      图解

      (令start = 1)
      开始时我们把dis[start]初始化为0,其余点初始化为inf

      第一轮循环找到dis值最小的点1,将1变成白点,对所有与1相连的蓝点的dis值进行修改,使得dis[2]=2,dis[3]=4,dis[4]=7

      第二轮循环找到dis值最小的点2,将2变成白点,对所有与2相连的蓝点的dis值进行修改,使得dis[3]=3,dis[5]=4

      第三轮循环找到dis值最小的点3,将3变成白点,对所有与2相连的蓝点的dis值进行修改,使得dis[4]=4

      接下来两轮循环分别将4,5设为白点,算法结束,求出所有点的最短路径

    为什么dijkstra不能处理有负权边的情况?

    我们来看下面这张图

    2到3的边权为−4,显然从1到3的最短路径为−2 (1->2->3).但在循环开始时程序会找到当前dis值最小的点3,并标记它为白点.
    这时的dis[3]=1,然而1并不是起点到3的最短路径.因为3已经被标为白点,所以dis[3]不会再被修改了.我们在边权存在负数的情况下得到了错误的答案.

    dijkstra的堆优化?

    观察dijkstra的流程,发现步骤2可以优化
    怎么优化呢?
    我们可以用堆对disdis数组进行维护,用O(logn)的时间取出堆顶元素并删除,用O(logn)的时间遍历每条边,总复杂度O((n+m)\log^2 n).
    附一下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    #include<queue>
    #define ll long long
    #define INF 2147483647

    using namespace std;

    struct node{
    int k,dis;
    bool operator < ( const node &x )const{return x.dis < dis;}
    };

    priority_queue<node> que;

    long long n,m,s,d[1000005],cnt,D[1000005],v[1000005];

    struct Edge{
    int to,next,x;
    }edge[2000005];

    void add(int x,int y,int a)
    {
    edge[++cnt].to = y;
    edge[cnt].x = a;
    edge[cnt].next = d[x];
    d[x] = cnt;
    }

    int main()
    {
    int x,y,a;
    scanf("%lld%lld%lld",&n,&m,&s);
    for(register int i = 1; i <= m; i++)
    {
    scanf("%d%d%d",&x,&y,&a);
    add(x,y,a);
    }
    que.push((node){s,0});
    for(register int i = 1; i <= n; i++) D[i] = INF;
    D[s] = 0;
    while(!que.empty())
    {
    node u = que.top();
    que.pop();
    if(v[u.k]) continue;
    v[u.k] = 1;
    for(register int i = d[u.k]; i; i = edge[i].next)
    {
    if(D[edge[i].to] > D[u.k] + edge[i].x)
    {
    D[edge[i].to] = edge[i].x + D[u.k];
    if(!v[edge[i].to]) que.push((node){edge[i].to,D[edge[i].to]});
    }
    }
    }
    for(register int i = 1; i <= n; i++) printf("%d ",D[i]);
    printf("\n");
    return 0;
    }
    Author: wflight
    Link: http://yoursite.com/2019/08/02/Dijkstra-学习/
    Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
    Donate
    • 微信
    • 支付寶

    Comment