【算法笔记】单源最短路Dijkstra

何为Dijkstra

单源最短路,即从一个点出发,到其他所有点的最短距离,起点可以是任意一点

Dijkstra的本质是贪心

过程

在这张图中,如果我们要\(1\)号点位起点\(st\),求最短路的过程大概是这样的

定义一个d数组,其中\(d[i]\)代表从起点\(st\)\(i\)的最短距离,首先认为\(st\)到所有点的距离都是\(inf\),也就是我们要做的初始化

初始化之后就变成了这样

从当前点出发,找所有和它邻接的点,看能否用这条边更新最短路的长度

先从\(1\)号点出发更新\(2\)号点,我们发现\(0+1<inf\),于是我们欣然接受这个方案,将\(2\)号点的距离更新成\(1\),也就是这样

同理我们也可以更新\(3\)号点的距离

更新完\(3\)号点后,\(1\)号点的所有邻接点全部更新完毕,也可以说\(1\)号点的使命结束,可以删掉了

此时这张图就变成了这样

然后我们再选择一个和原点距离最近的点(也就是\(2\)号点)进行更新

和上面同理,我们可以对\(3\)号点和\(4\)号点进行更新

我们并不需要关心具体需要怎样走,我们只关心从\(2\)号点到\(4\)号点时\(1+2<inf\)以及\(2\)号点到\(3\)号点时\(1+2<4\),新的距离比原来的距离短,我们就进行更新

此时\(2\)号点使命结束,删除

我们注意到\(3\)\(4\)到原点的距离相同,此时随便选一个进行拓展即可

拓展\(5\)号点同理

拓展完的图就变成了这样

此时我们再看这句话,相信你就能够理解

Dijkstra的本质是贪心

其中,贪心体现为每次都找最近点进行拓展,可以保证每次开始拓展时,这个点到原点的距离一定是最小的

此处可以进行反证:

如果这个点的距离不是最小的,就一定能找到另一个点离\(st\)更近来进行拓展

简单总结一下

Dijkstra的过程大概可以分为这样几步:

  1. 初始化\(d\)数组

  2. 找出距离最小的点\(u\)

  3. 遍历\(u\)的所有出边,并进行更新

    还没懂的话可以看看这里

朴素版代码

ll d[N];//d[i]为原点到点i的最短距离
void dijkstra(int x)//x是原点
{
    memset(d, 0x3f, sizeof(ll) * (n + 1));
    d[x] = 0;//自己到自己的距离为0
    bitset<N> vis;//true表示已经拓展过

    for(int i = 1; i <= n - 1; ++ i)
    {
        //找出最小点(距离源点最近的点)
        int u = 1;
        for(int j = 1; j <= n; ++ j)
            if(vis[u] || (!vis[j] && d[j] < d[u]))u = j;
        
        vis[u] = true;//表示u已经拓展过

        //此时d[u]已为最优
        for(const auto &[v, w] : g[u])
            if(!vis[v] && d[v] > d[u] + w)d[v] = d[u] + w;
    }
}

上面这段代码的时间复杂度为\(O(n^2)\),能够计算的\(n\)的范围大约是\(10^4\)以内,在大多数情况下并不能过题,所以我们就需要对一些步骤进行优化

很明显,初始化和遍历所有出边这两步并没有什么方案可以优化,我们便把目光放在第二步

优化

插入元素,查询最小值,删除最小值

这几步操作很容易让我们想到堆,也就是优先队列

我们可以用距离为第一关键字进行比较,并存入一个小根堆,这样我们查询最小值时就可以优化到\(log\)级别

堆优化代码


struct node
{
    ll x, w;//x表示出点 w表示权值
    bool operator < (const node &v)const//按照w小的优先
    {
        return w == v.w ? x < v.x : w > v.w;
    }
};

vector<node> g[N];

ll d[N];
int n, m;

void dijkstra(int x)
{
    memset(d, 0x3f, sizeof(ll) * (n + 1));
    d[x] = 0;//初始化起点
    bitset<N> vis;//表示已经拓展过

    priority_queue<node> pq;//定义了一个大根堆,但是重载运算符时进行了反转,也就是堆顶元素的距离最小

    pq.push({x, d[x]});//将起点作为拓展点

    while(!pq.empty())
    {
        int x = pq.top().x;pq.pop();//取出距离最小的元素

        if(vis[x])continue;//跳过冗余的拓展点
        vis[x] = true;//取出的x点已经得到了最短距离d[x]

        for(const auto &[v, w] : g[x])
        {
            if(!vis[v] && d[v] > d[x] + w)//如果vis[y],说明y已经得到了最短距离,无需更新
            {
                d[v] = d[x] + w;
                pq.push({v, d[v]});
            }
        }
    }
}
posted @ 2023-11-21 16:38  ShadowDream  阅读(53)  评论(0)    收藏  举报