最短路(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;
}