最短路径问题
从图中的某个顶点出发到达另外一个顶点的所经过的边的权重和最小的一条路径,称为最短路径。
解决方法:
SPFA算法
算法特点:用于解决单源最短路径问题,并且具有判断负圈的功能。(本来Bellman-Ford算法是用数组解决的,而我偷懒用了vector来方便我加边)SPFA利用队列对Bellman-Ford进行了优化,舍去了一些无效的操作,效率要高很多。
算法思路:回顾一下Bellman-Ford的算法,仅针对相邻节点计算最短近距离虽然对于FLoyd已经舍去了很多的不必要计算,但仍然存在很多无效计算。而SPFA的优化在于,计算节点u之后,下一步只计算和调整它的邻居,这样能加快收敛的过程。
变化在与增设一个队列vector<edge>e[num],用于存放以节点i为起点的边。
struct edge{
int u, v, w;
edge(int a, int b, int c){u=a;v=b;w=c;}
};
vector<edge>e[num];
for(int i=0; i<e[u].size(); i++){
int v = e[u][i].v, w = e[u][i].w;
if(dis[v] > dis[u] + w){
……
}
}
其他的思路和步骤都是与Bellman-Ford雷同的,不再赘述。
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
const int num = 10001;
const int inf = 0x3f3f3f3f;
int n, m;
int dis[num];
int pre[num];
struct edge{
int u, v, w;
edge(int a, int b, int c){u=a;v=b;w=c;}
};
vector<edge>e[num];
void print_path(int u, int v){
if(u==v){ printf("%d", u);return;}
print_path(u, pre[v]);
printf("->%d", v);
}
void spfa(){
int neg[num];//判断负圈
int inq[num];//判断是否在队列中
for(int i=1; i<=n; i++){
dis[i] = inf;
neg[i] = inq[i] = 0;
}
int s = 1;//定义起点为1
queue<int>q;
q.push(s);
inq[s] = 1;//s进队
while(!q.empty()){
int u = q.front();
q.pop();
inq[u] = 0;//u出队
for(int i=0; i<e[u].size(); i++){
int v = e[u][i].v, w = e[u][i].w;
if(dis[v] > dis[u] + w){
dis[v] = dis[u] + w;
pre[v] = u;
if(!inq[v]){
q.push(v);
inq[v] = 1;
neg[v]++;
if(neg[v]>n){printf("有负圈\n");return ;}
}
}
}
}
}
int main(){
while(~scanf("%d%d", &n, &m) &&n &&m){
for(int i=1; i<=n; i++) e[i].clear();
while(m--){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
e[a].push_back(edge(a, b, c));
e[b].push_back(edge(b, a, c));
}
spfa();
}
return 0;
}
这个基于邻接表的代码已经很好了,但是在极端情况下,图特别大,用邻接表会超出空间限制,此时就需要用链式前向星来存图。主要的区别在于:不再用vector来记录同起点的边,而是直接在结构体下定义数组,增设一个函数来加边。
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int inf = INT_MAX/10;
const int num = 1000005;
int n, m, cnt;
int head[num];
int dis[num];
int pre[num];
int inq[num];
int neg[num];
struct edge{ int to, w, next;}e[num];
void print_path(int u, int v){
if(u==v) printf("%d", u);
print_path(u, pre[v]);
printf("->%d", v);
}
void init(){
for(int i=0; i<num; i++)
head[i] = e[i].next = -1;
cnt = 0;
}
void addedge(int u, int v, int w){
e[cnt].to = v;
e[cnt].w = w;
e[cnt].next = head[u];//指向上一条同起点的边
head[u] = cnt++;//存放以u为起点的某条边edge的下标
}
void spfa(){
for(int i=1; i<=n; ++i){
neg[i] = inq[i] = 0;
dis[i] = inf;
}
int s = 1;//设起点为1
neg[s] = 1;
dis[s] = 0;
queue<int>q;
q.push(s);
inq[s] = 1;//入队
while(!q.empty()){
int u = q.front();
q.pop();
inq[u] = 0;//出队
//加入的边是从后往前遍历的
for(int i=head[u]; ~i; i=e[i].next){//~i 等同于 i!=-1
int v = e[i].to, w = e[i].w;
if(dis[v] > dis[u] + w){
dis[v] = dis[u] + w;
pre[v] = u;
if(!inq[v]){
inq[v] = 1;
q.push(v);
neg[v]++;
if(neg[v] > n) { printf("有负圈\n"); return ;}
}
}
}
}
}
int main(){
while(~scanf("%d%d", &n, &m) &&n &&m){
init();
while(m--){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
addedge(a, b, c);
addedge(b, a, c);
}
spfa();
printf("%d\n", dis[n]);
}
return 0;
}