蓝桥杯试题集 最短路径(SPFA算法)

最短路(SPFA)


资源限制

时间限制:1.0s 内存限制:256.0MB

问题描述

给定一个n个顶点,m条边的有向图(其中某些边权可能为负,但保证没有负环)。请你计算从1号点到其他点的最短路(顶点从1到n编号)。

输入格式

第一行两个整数n, m。

接下来的m行,每行有三个整数u, v, l,表示u到v有一条长度为l的边。

输出格式

共n-1行,第i行表示1号点到i+1号点的最短路。

样例输入

3 3
1 2 -1
2 3 -1
3 1 2

样例输出

-1
-2

数据规模与约定

对于10%的数据,n = 2,m = 2。

对于30%的数据,n <= 5,m <= 10。

对于100%的数据,1 <= n <= 20000,1 <= m <= 200000,-10000 <= l <= 10000,保证从任意顶点都能到达其他所有顶点。


最初是用迪杰斯特拉(Dijkstra) + 模拟邻接表做的,然后40分且还有超时错误,遂搜题,发现几乎都是用SPFA(Bellman-Ford算法的队列优化算法)算法做,学习了一番之后通过。小菜鸡总结如下。


模拟邻接表

忘了看到哪位大佬的方法,感觉很厉害巧妙简便,于是学习之。精髓是利用vector容器。

typedef struct point {
    int vex,value;
}Point;
//Point用来存区所指向顶点的编号和这条边的权值
vector<Point*> G[20005];         //模拟邻接表,G[i]相当于邻接表的行,每个G[i]是一个容器
void CreateG(int n, int m) {
    int u, v, l;
    while (m--) {
        scanf("%d%d%d",&u,&v,&l);
        Point* p = new Point;
        p->vex = v;
        p->value = l;
        G[u].push_back(p);      //向G[u]容器中添加顶点u所指向顶点
    }
}

Spfa(Bellman-Ford算法的队列优化算法)

用队列q来保存待优化的结点,res数组保存1号顶点到每个顶点的最小权值,visit数组保存每个顶点的在队情况,Inf=0x7FFFFFFF作为无穷大。以下图为例推演整个过程。
在这里插入图片描述

在一切没有发生之前:
在这里插入图片描述

int res[20005];
int visit[20005];
queue<int> q;
const int Inf = 0x7fffffff;

for (int i = 1; i < n+1; i++) //顶点从1开始,res[0]不管
        res[i] = Inf;

开始:

res[1]=0,顶点1入队,visit[1]=1。因为1为第一个顶点,入队后便出队,开始后面的过程。

顶点1出队,visit[1]=0,而1指向2,3顶点,权值分别为10,2,小于res[2]、res[3],更新res[2]=10,res[3]=2,再判断顶点2,3是否在队列中,不在则入队。现在队列为:2,3,现在res数组为:

(res[1]=0、出队后重置visit、先判断是否更新再判断是否要入队的原因后面会分析)
在这里插入图片描述

顶点2出队,visit[2]=0,而2指向4,6顶点,权值分别为2,4。到此为止顶点1到顶点4的最小权值是res[4]= ∞,路径1->2->4权值为res[2] + 2 = 12 < 无穷大,所以更新res[4]=12。同理路径1->2->6权值等于res[2]+4=14< ∞,更新res[6]=14,即相当于把2作为跳板。再判断顶点4,6是否在队列中,不在则入队。现在队列为:3,4,6,现在res数组为:
在这里插入图片描述

顶点3出队,visit[3]=0,而3指向2,5顶点,权值分别为2,2。到此为止,顶点1到顶点2的最小权值是res[2]为10,现判断1->3->2路径权值是否可以小于res[2],此权值等于res[3] + 2 = 4 < 10,所以更新res[2]=4。顶点1到顶点5的最小权值为res[5] = ∞,先判断1->3->5路径权值是否可以小于res[5],此权值等于res[3] + 2 =4 < ∞,所以更新res[5]=4,即相当于把3作为跳板。再判断2,5是否在队列中(visit[2]已被重置为0),不在则入队。现在队列为:4,6,2,5,现在res数组为:

(关于res[1]=0:顶点1出队中,我并没有进行路径权值比较,但其实是存在的,可以理解为路径是1->1->2,即res[1]+10)

(关于visit重置:假如上一段中visit[2]没有再次入队,那么无法更新最短路径1->3->2->4和1->3->2->6。因为res[4]是在res[2]=10的情况下得出的,而3出队后,res[2]又被更新为更小值4了。res[4]将会等于12,res[6]将会等于9,显然不是最小,这两个值的路径分别为1->2->4,1->3->5->6,)

(关于先判断是否更新再判断是否要入队:1.有可能会陷入死循环 2.同上一条,假如1->3->2的权值等于或大于原来的res[2]=10,即res[2]的权值没有更新,那就没有必要将2入队了,不会出现上条的结果 )
在这里插入图片描述

后面就是4,6,2,5出队再进行如上一样的过程,就不赘述了,最后res结果为:
在这里插入图片描述

完整代码

#include<cstdio>
#include<iostream>
#include<vector>
#include<queue>

using namespace std;
int res[20005];
int visit[20005];
const int Inf = 0x7fffffff;
queue<int> q;
typedef struct point {
    int vex,value;
}Point;
vector<Point*> G[20005];

void CreateG(int n, int m) {
    int u, v, l;
    while (m--) {
        scanf("%d%d%d",&u,&v,&l);
        Point* p = new Point;
        p->vex = v;
        p->value = l;
        G[u].push_back(p);
    }
}
void Spfa() {
    visit[1] = 1;
    res[1] = 0;
    q.push(1);
    while (!q.empty()) {
        int v = q.front();
        q.pop();
        visit[v] = 0;
        for (int i = 0; i < G[v].size(); i++) {
            if (G[v][i]->value + res[v] < res[G[v][i]->vex]) {
                res[G[v][i]->vex] = G[v][i]->value + res[v];
                if (!visit[G[v][i]->vex]) {
                    q.push(G[v][i]->vex);
                    visit[G[v][i]->vex] = 1;
                }
            }     
        }
    }
}

int main() {
    int n, m;
    cin >> n >> m;
    
    CreateG(n,m);
    for (int i = 1; i < n+1; i++)
        res[i] = Inf;
    
    Spfa();
    for(int i = 2;i < n+1;i++)
        cout << res[i] << endl;
    
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值