自己对最短路的简单总结并不涉及详解
1.Dijkstra
算法思想:(求单源最短路,朴素算法复杂度O(n^2),堆优化O(nlogn)
将点分为两类,一类为已被更新最短距离的点为“标记点”,另一类为还没有被更新最短距离的点为“未标记点”。初始dist[i]=INF
一开始将起点到起点的距离标记为0,加入优先队列(dist从小到大排序);每次取dist最小的点(即与起始点距离最小的点)设为“标记点”,然后枚举和该点相连的点,更新他们的最短路dist[i],更新的原则就是以该点为中转点,更新方程为dist[i]=min(dist[j]+nw,dist[i]) [ j为中转点,i为更新点,w为j到i的权值 ] 然后将与该点相关联的点都加入队列,循环往复,直到队列为空。最后全部的点都访问完毕时,dist都被更新,即起点到到该点的最短路。
//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<queue>
#include<stack>
#include<map>
#include<string>
#include<cstring>
#include<algorithm>
#include<limits.h>
using namespace std;
typedef long long ll;
const ll INF=LONG_LONG_MAX;
const ll inf=LONG_LONG_MIN;
const ll maxn=3e5+5;
ll n,m,ss,ee;
struct node {
ll p; //当前点
ll dis; //起始点到该点的最短距离
node(ll np=0,ll ndis=0) {
p=np,dis=ndis;
}
bool operator <(const node& x)const {
return dis>x.dis;
}
};
struct edge {
ll id;
ll to;
ll w;
edge(ll nid=0,ll nto=0,ll nw=0) {
id=nid,to=nto,w=nw;
}
};
vector<edge> e[maxn];
bool vis[maxn];
ll dist[maxn];
void Dijkstra(int start) {
memset(dist, 0x3f, sizeof(dist));
memset(vis, false, sizeof(vis));
dist[start]=0;
priority_queue<node> q;
q.push(node(start,0));
while(!q.empty()) {
node tmp=q.top();
q.pop();
int np=tmp.p;
if(vis[np])
continue;
else {
vis[np]=1;
for(int i=0; i<e[np].size(); ++i) {
edge ne=e[np][i];
ll nto=ne.to;
ll nw=ne.w;
ll nid=ne.id;
if(dist[nto]>dist[np]+nw) {
dist[nto]=dist[np]+nw;
q.push(node(nto,dist[nto]));
}
}
}
}
}
int main() {
scanf("%lld%lld%lld%lld",&n,&m,&ss,&ee);
ll x,y,w;
for(int i=1; i<=m; ++i) {
scanf("%lld%lld%lld",&x,&y,&w);//无向图建图
e[x].push_back(edge(i,y,w));
e[y].push_back(edge(i,x,w));
}
Dijkstra(ss);
cout<<dist[ee]<<endl;
return 0;
}
例题:https://www.luogu.org/problemnew/show/P2384
//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<queue>
#include<stack>
#include<map>
#include<string>
#include<cstring>
#include<algorithm>
#include<limits.h>
using namespace std;
typedef long long ll;
const ll INF=LONG_LONG_MAX;
const ll inf=LONG_LONG_MIN;
const ll maxn=3e5+5;
const ll mod=9987;
ll n,m,k;
struct node {
ll p; //当前点
ll dis; //起始点到该点的最短距离
node(ll np=0,ll ndis=0) {
p=np,dis=ndis;
}
bool operator <(const node& x)const {
return dis>x.dis;
}
};
struct edge {
ll id;
ll to;
ll w;
edge(ll nid=0,ll nto=0,ll nw=0) {
id=nid,to=nto,w=nw;
}
};
vector<edge> e[maxn];
bool vis[maxn];
ll dist[maxn];
void Dijkstra() {
memset(dist, 0x3f, sizeof(dist));
memset(vis, false, sizeof(vis));
dist[1]=0;
priority_queue<node> q;
q.push(node(1,0));
while(!q.empty()) {
node tmp=q.top();
q.pop();
int np=tmp.p;
if(vis[np])
continue;
else {
vis[np]=1;
for(int i=0; i<(int)e[np].size(); ++i) {
edge ne=e[np][i];
ll nto=ne.to;
ll nw=ne.w;
ll nid=ne.id;
if(dist[np]==0){
dist[nto]=(dist[np]+nw)%mod;
q.push(node(nto,dist[nto]));
}
else if(dist[nto]>dist[np]*nw) {
dist[nto]=(dist[np]*nw)%mod;
q.push(node(nto,dist[nto]));
}
}
}
}
}
int main() {
scanf("%lld%lld",&n,&m);
ll x,y,w;
for(int i=1; i<=m; ++i) {
scanf("%lld%lld%lld",&x,&y,&w);//有向图建图
e[x].push_back(edge(i,y,w));
}
Dijkstra();
cout<<dist[n]%mod<<endl;
return 0;
}
2.Floyd
算法思想:(求多源最短路,算法复杂度O(n^3)
3层循环,第一层枚举中间点k(以k为中转点),第二层与第三层枚举两个端点i,j。若有dis[i][j] > dis[i][k] + dis[k][j] 则把dis[i][j]更新成dis[i][k] + dis[k][j]。不断去更新dis[i][j],就是一种dp的思想,因为算法复杂度高,一般只有数据量小的时候才用得上。
//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<queue>
#include<stack>
#include<map>
#include<string>
#include<cstring>
#include<algorithm>
#include<limits.h>
using namespace std;
typedef long long ll;
const ll INF=INT_MAX;
const ll inf=INT_MIN;
const ll maxn=1e3+5;
ll n,m;
ll e[maxn][maxn];
int main() {
scanf("%lld%lld",&n,&m);
for(int i=1; i<=n; ++i) {
for(int j=1; j<=n; ++j) {
if(i==j) e[i][j]=0;
else e[i][j]=INF;
}
}
ll x,y,w;
for(int i=1; i<=m; ++i) {
scanf("%lld%lld%lld",&x,&y,&w);//有向图建图
e[x][y]=w;
}
for(int k=1; k<=n; k++) { //经过k点
for(int i=1; i<=n; i++) { //枚举起点终点,看路径能不能减少
for(int j=1; j<=n; j++)
if(e[i][j]>e[i][k]+e[k][j])
e[i][j]=e[i][k]+e[k][j];
}
}
for(int i=1; i<=n; i++) {//从i到j的最短距离
for(int j=1; j<=n; j++) {
printf("%lld",e[i][j]);
}
printf("\n");
}
return 0;
}
例题:https://www.luogu.org/problemnew/show/P2888
//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<queue>
#include<stack>
#include<map>
#include<string>
#include<cstring>
#include<algorithm>
#include<limits.h>
using namespace std;
typedef long long ll;
const ll INF=INT_MAX;
const ll inf=INT_MIN;
const ll maxn=1e3+5;
ll n,m,k;
ll e[maxn][maxn];
int main() {
scanf("%lld%lld%lld",&n,&m,&k);
for(int i=1; i<=n; ++i) {
for(int j=1; j<=n; ++j) {
if(i==j) e[i][j]=0;
else e[i][j]=INF;
}
}
ll x,y,w;
for(int i=1; i<=m; ++i) {
scanf("%lld%lld%lld",&x,&y,&w);
e[x][y]=w;
}
for(int k=1; k<=n; k++) {
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++) {
e[i][j]=min(max(e[i][k],e[k][j]),e[i][j]);
}
}
}
for(int i=1; i<=k; i++) {
scanf("%lld%lld",&x,&y);
if(e[x][y]==INF) cout<<-1<<endl;
else cout<<e[x][y]<<endl;
}
return 0;
}
3.Bellman-Ford
算法思想:Bellman-Ford也是来算单源最短路的,但是复杂度是O(v*e),是高于Dijkstra的,但是它可以用来检测负权回路,和带负值的最短路。
就是经过v-1次松弛(初始点固定了,最差的情况也是每一次松弛只松弛一个点,有v-1个),每次去尝试所有的边能否去松弛各个点。松弛过后,如果再进行一次松弛还能被松弛的话,则说明存在负权回路。
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=1e5+5;
int dist[maxn];
int v,e;
//顶点和边
struct edge {
int s;
int e;
int w;
};
vector<edge> tt;
bool bellmanFord(int src) {
memset(dist,0x3f,sizeof(dist));
dist[src]=0;
for(int i=1; i<v; ++i) {
for(int j=0; j<e; ++j) {
int s=tt[j].s;
int e=tt[j].e;
int w=tt[j].w;
if(dist[s]!=INF && dist[e]>dist[s]+w) {
dist[e]=dist[s]+w;
}
}
}
//检测负权回路
bool isBack=false;
for(int j=0; j<e; ++j) {
int s=tt[j].s;
int e=tt[j].e;
int w=tt[j].w;
if(dist[s]!=INF && dist[e]>dist[s]+w) {
isBack=true;
break;
}
}
return isBack;
}
int main() {
cin>>v>>e;
int ns,ne,nw;
for(int i=0; i<e; ++i) {
cin>>ns>>ne>>nw;
tt.push_back(edge {ns,ne,nw});
}
bool res=bellmanFord(0);
if(res) {
cout<<"有负权回路"<<endl;
} else {
cout<<"无负权回路"<<endl;
for(int i=1; i<=v; ++i) {
cout<<dist[i]<<' ';
}
cout<<endl;
}
return 0;
}
4.Spfa
算法思想:SPFA 算法是 Bellman-Ford算法 的队列优化算法的别称,通常用于求含负权边的单源最短路径,以及判负权环。
用一个先进先出的队列来维护,每次取出队首结点p,然后用该点去松弛和它相连的点to,如果到dist[to]被减小,并且此时队列中没有这个点,那就将其加入队列(因为,队列只是维护的点,假设t已经在队列中,相同的点t存储的dist信息是一样的,这里dist[t]被减小,再将t点加入队列毫无意义),直到队列为空。
*判断负权环:如果一个点被加入队列次数超过顶点数V,则存在负权环。
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=1e5+5;
int dist[maxn];//距离
int pushnum[maxn];//入队次数
bool vis[maxn];//是否入队中
struct edge {
int to;
int w;
};
vector<edge> tt[maxn];
int N,M,S;
//点数,边数,起点
bool spfa(int start) {
memset(dist,0x3f,sizeof(dist));
memset(vis,false,sizeof(vis));
memset(pushnum,0,sizeof(pushnum));
dist[start]=0;
vis[start]=true;
pushnum[start]++;
queue<int> q;
q.push(start);
while(!q.empty()) {
int p=q.front();
q.pop();
vis[p]=false;
for(int i=0; i<(int)tt[p].size(); ++i) {
edge tmp=tt[p][i];
if(dist[tmp.to]>dist[p]+tmp.w) {
dist[tmp.to]=dist[p]+tmp.w;
if(vis[tmp.to]==false) {
q.push(tmp.to);
vis[tmp.to]=true;
pushnum[tmp.to]++;
if(pushnum[tmp.to]>N) {
return false;
}
}
}
}
}
return true;
}
int main() {
cin>>N>>M>>S;
int x,y,z;
for(int i=0; i<M; ++i) {//有向图建图
cin>>x>>y>>z;
tt[x].push_back(edge {y,z});
}
if(spfa(S)) {
for(int i=1; i<=N; ++i) {
cout<<dist[i]<<' ';
}
cout<<endl;
}
else{
cout<<"有负环"<<endl;
}
return 0;
}