迪杰斯特拉的缺点
我们知道迪杰斯特拉算法无法处理所有有负边的情况,那让我们先复习一下,为什么迪杰斯特拉无法处理
类似这样的情况,我们在遍历1号点的出边时,将 dist[2] = 1 , dist[3] = 2;但在下一次循环找最小的dist时就会把2号点归到最短路径当中,其dist[2]=1的值就不会再被改变,但实际上有更短的路径,1->3->2最短路径为0!这就体现出了迪杰斯特拉算法无法处理所有负边的缺点。
贝尔曼福特算法(Bellman-Ford)
而Bellman-Ford就可以解决这一点,它遍历所有的节点和其出边来找到最短路径,听起来确实很笨,但确确实实解决了我们处理负边的问题,思路就是这么简单,下面我在代码中一步一步来解释如何实现。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int dist[N],backup[N];
struct edge{
int a,b,c;
};//用来记录我们的每一组边及其权重
edge edges[N];
int n ,m ,k ;//我们寻找经过k条边从1号点走到第n号点所需要的最短距离
void bellman_ford(){
memset(dist, 0x3f,sizeof dist);
dist[1]=0;
for(int i=0 ;i<k ;i++){
memcpy(backup ,dist ,sizeof dist);
//记录备份,使得在更新时所用的都是还未经历这次更新时的值
//防止出现用更新过的数来更新,我们要保证只能更新这一条边
for(int j=0;j<m ;j++){
int a=edges[j].a,b=edges[j].b,c=edges[j].c;
dist[b]=min(dist[b],backup[a]+c);//松弛操作
}
}
if (dist[n]>0x3f3f3f3f/2) cout<<"impossible"<<endl;
//因为有负权的存在,所以若不存在最短路径时,可能会使dist[n]=0x3f3f3f3f的值减小,
//所以根据实际情况,只要大于其一般就可以认为其无最短路径了
else cout<<dist[n]<<endl;
}
int main(){
cin>>n>>m>>k;
for(int i=0 ; i < m ; i ++){
cin>>edges[i].a>>edges[i].b>>edges[i].c;
}
bellman_ford();
return 0;
}
spfa
spfa算法其实是对BF算法的优化,在BF中我们可以看到,我们遍历了每一个节点和每一条边,这其中实际上有很多重复的部分,而spfa就是对这一部分进行了优化。我们来思考这样一个问题,为什么我们在寻找最短路径的过程中,最短路径会不断改变,这是因为我们对于一些点的dist找到了更小的值,才使得在之后的路径中找到了更短的路经,由此而看,我们只需要对那些dist值发生改变的点进行操作就不就足够了么!
这里我们采用队列来进行优化:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#define endl '\n'
using namespace std ;
typedef long long ll;
typedef pair<int,int> PII;
const int N = 1e5 + 10;
int n,m;
int dist[N];
bool st[N];//在更新最短距离时,判断最短距离是否确定
int h[N],ne[N],e[N],w[N],idx;
void add(int a,int b ,int c){//用邻接表来存边
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx++;
}
int spfa(){
memset(dist,0x3f,sizeof dist);
queue<int> q;
q.push(1);
dist[1]=0;
st[1]=true;
while(q.size()){
int t=q.front();
q.pop();
st[t]=false;
//因为在后续可能找到更小的路径,所以要使其为false,之后出现更新情况时再次入队
for(int i = h[t] ; i != -1 ; i=ne[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 ;
}
}
}
}
}
int main(){
memset(h,-1,sizeof h);
cin>> n >> m ;
int a, b ,l;
while(m--){
cin>>a>>b>>l;
if(a!=b){
add(a,b,l);
}
}
spfa();
if(dist[n] == 0x3f3f3f3f) cout<<"impossible"<<endl;
//在不存在最短路径的情况下,在有负边时,dist[n]的值是不会更新的
//dist[j]>dist[t] + w[i]这一步中dist[t]为一很大的数,减去一个数虽然确实树值上减小了
//但右式仍大于dist[j],所以也就不进行更新赋值,也就不会dist[n]的值进行改变
else cout<<dist[n]<<endl;
return 0;
}