各种最短路问题的求解方式


在这里插入图片描述

限制:bellman_ford 和 spfa 可用于判断负环, 其他算法只能用于无负环情况。

朴素dijkstra算法

(ps: 因为时稠密图,我们用邻接矩阵来存图)

设定dist[i]为点i到1的最短距离
st[i]为该点的最短距离是否已经确定

假设我们要求点1到点n的最短距离
先将dist数组初始化为*INF*,*dist[1] = 0*;
循环n次
每次取出所有点中dist最小的点t,并将其标记st[t] = true
通过邻接矩阵用点t来更新其他的点(dist[j] = min(dist[j], dist[t] + g[t][j]))

由于每次可以确定一个点的dist
循环n次可以确定所有点的dist
最终返回dist[n]即可,如果可能不存在最短路径就判断是否为INF

总结:每次取出未确定dist点中dist最小的点更新其他点

代码:

int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    for(int i = 0; i < n; i++)
    {
        int t = -1;//还未确定最短路的点中距离最小的
        for(int j = 1; j <= n; j++)
        {
            if(!st[j] && (t == -1 || dist[t] > dist[j]))//更新t
                t = j;
        }
        st[t] = true;
        //利用t更新其他的点
        for(int j = 1; j <= n ; j++)
            dist[j] = min(dist[j], dist[t] + g[t][j]);
    }

    if(dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

复杂度分析:
两重n次循环(o(n^2))

堆优化版的dijkstra算法

优化思路:
由于是稀疏图,我们使用邻接表储存图,
每次取出st==false的dist最小值的点, 使用优先队列(堆)来储存
每次更新从堆中取出点有连边的点

总结:
用堆和邻接表优化

代码:

int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, 1});//first储存距离,second储存节点编号
    while(heap.size())
    {
        auto t = heap.top();//取距离最小的点
        heap.pop();
        int ver = t.second, distance = t.first;

        if(st[ver]) continue;
        st[ver] = true;
        //依次更新连接ver的点
        for(int i = h[ver]; i != -1; i = nex[i])
        {
            //如果可以更新就更新距离,并推入堆中
            int j = e[i];
            if(dist[j] > distance + w[i])
            {
                dist[j] = distance + w[i];
                heap.push({dist[j], j});
            }
        }
    }

    if(dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

复杂度分析:
遍历每一条边m次,每次推入堆中logn,总的mlogn

Bellman_Ford算法

算法思路:
循环k次(经过不超过k条边的最短距离)
每次循环遍历所有的边(设边的起点为a, 终点为b, 权值为w),更新dist[b] = min(dist[b], dist[a] + w)
所以我们可以直接用struct存边
存在问题:
在这里插入图片描述
所以我们可以创建一个backu数组储存上一次dist的结果,用上一次的结果来更新

然后由于负权边的存在,最后的答案可能会比INF小一点

代码:

int Bellman_Ford()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    for(int i = 1; i <= k ; i++)//经过不超过k条边的最短距离
    {
        memcpy(backup, dist, sizeof dist);
        for(int j = 0; j < m; j++)
        {
            int a = edges[j].a, b = edges[j].b, w = edges[j].w;
            dist[b] = min(dist[b], backup[a] + w);//防止串连
        }
    }

    if(dist[n] > 0x3f3f3f3f/2) return -1;//存在负权边导致初始化的值有可能变小
    return dist[n];
}

如何判断负环:
如果k = n时仍有更新情况,则存在负环

spfa算法

spfa其实是bellman_floyd的一个优化

算法思路:
由dist[b] = min(dist[b], backup[a] + w)可知,当a的dist发生变化时b才需要更新
同样地,我们依旧可以用邻接表储存边,并用队列维护dist发生了改变的点,每次取出队头元素更新连接的点。

代码:

int spfa()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    queue <int> q;
    q.push(1);
    st[1] = true;

    while(q.size())
    {
        int t = q.front();
        q.pop();

        st[t] = false;

        for(int i = h[t]; i != -1; i = nex[i])
        {
            int j = e[i];
            if(dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                if(!st[j])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    //if(dist[n] == 0x3f3f3f3f) return -1;  防止最短路径就是-1,将此行删除
    return dist[n];
}

如何判断负环:
将所有点的dist都初始化为0,因为可能存在负环时1到不了的
cnt[x]存储1到x的最短路中经过的点数(除开自己)
然后如果存在cnt[x] >= n那么有负环
代码:

bool spfa()
{
    queue <int> q;
    for(int i = 1; i <= n ; i++)
    {
        q.push(i);
        st[i] = true;
    }
    while(q.size())
    {
        int t = q.front();
        q.pop();

        st[t] = false;

        for(int i = h[t]; i != -1; i = nex[i])
        {
            int j = e[i];
            if(dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;
                if(cnt[j] >= n) return true;
                if(!st[j])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    return false;
}

floyd算法

直接看题目把,floyd算法背过就好了
用d数组储存边的情况,然后背模板输出就好了

在这里插入图片描述

#include <bits/stdc++.h>

using namespace std;
const int N = 210, INF = 1e9;

int d[N][N];
int n, m, q;

void floyd()
{
    for(int k = 1; k <= n; k++)
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= n; j++)
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}

int main()
{
    scanf("%d%d%d", &n, &m, &q);

    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n ;j++)
            if(i == j) d[i][j] = 0;
            else d[i][j] = INF;

    while(m--)
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        d[a][b] = min(d[a][b], c);
    }

    floyd();
    while(q--)
    {
        int a, b;
        scanf("%d%d", &a, &b);

        if(d[a][b] >= INF/2) printf("impossible\n");
        else printf("%d", d[a][b]);
    }

    return 0;
}

acwing上的各种模板题:
朴素dijkstra
堆优化dijstra
bellman_ford
spfa
spfa判负环
floyd

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值