第三章 搜索与图论 最短路算法之SPFA算法

第三章 搜索与图论 最短路算法之SPFA算法,第1张

第三章 搜索与图论 最短路算法之SPFA算法 1、算法思路

这两个算法用于解决存在负权边的单源最短路问题。其中SPFA算法是Bellman-Ford算法的优化。尽管可以用于负权图中,但是对于存在负权环的图两个算法无法有效工作,但是可以利用这两个算法判断图中是否存在负权环。

1.算法流程

我们用数组dis记录每个结点的最短路径估计值,用邻接表或邻接矩阵来存储图G。
回忆一下我们 *** 作Bellman-Ford算法,核心的 *** 作是

对于图中的每条边uv:如果 u 到源点的距离 d 加上边的权值 w 小于 v 到源点的距离 d,则更新 v 到源点的距离值 d

可以写成如下的形式
d i s [ v ] = m i n ( d i s [ v ] , d i s [ u ] + w ) dis[v] = min(dis[v],dis[u] + w) dis[v]=min(dis[v],dis[u]+w)
初始时,所有的dis[i]都是正无穷。可以看到,dis[v]在未更新时是不变的,w也是固定的。如果dis[u]没有产生变化,那么dis[v]将不会产生变化。最开始状态下,由起点的距离dis[起点] = 0,带来了最初始的变化,受到初始起点影响的节点也产生了变化并影响其他节点。所以我们只需要将产生变化的节点存进一个队列中,并用这个节点检查每个与之相连的节点的距离,如果相连节点的距离产生变化,则同样入队。
总结一下,我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛 *** 作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛 *** 作,直至队列空为止

我们要知道带有负环的图是没有最短路径的,所以我们在执行算法的时候,要判断图是否带有负环,方法有两种:

开始算法前,调用拓扑排序进行判断(一般不采用,浪费时间)
如果某个点进入队列的次数超过N次则存在负环(N为图的顶点数)

2.小实例


取出队头元素,对其能到达的节点进行遍历,更新他们的长度,如果被更新并且还未在队列中,则节点元素入队。

同样进行 *** 作,取出队头3,进行更新

后面就是这样进行,即可完成该算法。

2、例题 1.spfa求最短路
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。

请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 impossible。

数据保证不存在负权回路。

输入格式
第一行包含整数 n 和 m。

接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。

如果路径不存在,则输出 impossible。

数据范围
1≤n,m≤10^5,
图中涉及边长绝对值均不超过 10000。

输入样例:
3 3
1 2 5
2 3 -3
1 3 4
输出样例:
2
#include
#include
#include
using namespace std;
const int N = 1e5 + 10;
int d[N],h[N],idx,e[N],ne[N],w[N];
//st用来记录该节点是否已经在队列中
bool st[N];
queue q;
void add(int a, int b, int x)
{
    e[idx] = b;
    ne[idx] = h[a];
    w[idx] = x;
    h[a] = idx ++;
}
int main()
{
    int n,m;
    cin >> n >> m;
    q.push(1);
    memset(d,0x3f,sizeof d);
    memset(h,-1,sizeof h);
    d[1] = 0;
    st[1] = true;
    for(int i = 0; i < m; i ++)
    {
        int a,b,x;
        cin >> a >> b >> x;
        add(a,b,x);
    }
    for(int i = 0; i < n; i ++)
    {
        while(q.size())
        {
            auto t = q.front();
            q.pop();
            st[t] = false;
            for(int i = h[t]; i != -1; i = ne[i])
            {
                if(d[e[i]] > d[t] + w[i])
                {
                    d[e[i]] = d[t] + w[i];
                    //如果更新了节点并且该节点没有在队列中,则入队
                    if(!st[e[i]])
                    {
                        st[e[i]] = true;
                        q.push(e[i]);
                    }
                }
            }
        }
    }
    if(d[n] > 0x3f3f3f3f / 2)cout << "impossible";
    else cout << d[n];
}
2.spfa判断负环
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。

请你判断图中是否存在负权回路。

输入格式
第一行包含整数 n 和 m。

接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式
如果图中存在负权回路,则输出 Yes,否则输出 No。

数据范围
1≤n≤2000,
1≤m≤10000,
图中涉及边长绝对值均不超过 10000。

输入样例:
3 3
1 2 -1
2 3 4
3 1 -4
输出样例:
Yes
#include
#include
#include
using namespace std;
const int N = 1e4 + 10;
int h[N],e[N],ne[N],idx,w[N],cnt[N],d[N];
bool st[N];

void add(int a, int b, int x)
{
    e[idx] = b;
    ne[idx] = h[a];
    w[idx] = x;
    h[a] = idx ++;
}

int main()
{
    int n,m;
    cin >> n >> m;
    memset(h,-1,sizeof h);
    //这里不需要将d初始化,因为我们不关系最后的距离
    for(int i  =0; i < m; i ++)
    {
        int a,b,x;
        cin >> a >> b >> x;
        add(a,b,x);
    }
    queue q;
    //这里要把所有起点全部入队,因为我们不知道哪一个点的路径会经过负环回路
    for(int i = 1; i <= n; i ++)
    {
        st[i] = true;
        q.push(i);
    }
    while(q.size())
    {
        auto t = q.front();
        q.pop();
        st[t] = false;
        for(int i = h[t]; i != -1; i = ne[i])
        {
            if(d[e[i]] > d[t] + w[i])
            {
                d[e[i]] = d[t] + w[i];
                //cnt用来记录到当前节点经过的边的数量
                cnt[e[i]] = cnt[t] + 1;
                if(cnt[e[i]] >= n)
                {
                    cout << "Yes";
                    return 0;
                }
                if(!st[e[i]])
                {
                    q.push(e[i]);
                    st[e[i]] = true;
                }
            }
        }
    }
    cout << "No";
}
参考资料
  1. 最短路径问题—SPFA算法详解
  2. Acwing

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/4748914.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-08
下一篇 2022-11-08

发表评论

登录后才能评论

评论列表(0条)

保存