最短路一共有4种,也不难,这里讲一下当做复习好了。我在这里讲的都是我自己的理解,如果有误大家可以提出,而这些算法的推导、实现过程建议还是去百科里看。
有关于算法的实现过程还是非常重要的,当你忘记怎么写的时候,记住简单的实现过程好歹还可以手推出来。这些我在代码里也会有讲解。
Floyd(百科):
最简单,最暴力的最短路,主要是一个dp的思想,也很好理解,优点是可以处理负边以及将任意两个点之间的最短路都求出来了,一般没什么用,时间复杂度Θ(n³)。
推一道例题HDU2544。
其实这道题完全可以用Dijsktra来做,但是n非常小,所以就直接上Floyd代码更短。
Code:
#include<bits/stdc++.h>
#define N 105
using namespace std;
int dist[N][N];
int inline read()
{
int f=1,x=0;char s=getchar();
while(s<'0'||s>'9')
{
if(s=='-')f=-1;
s=getchar();
}
while(s<='9'&&s>='0')
{
x=x*10+s-'0';
s=getchar();
}
x*=f;
return x;
}
int main()
{
int n=read(),m=read();
while(n!=0||m!=0)
{
memset(dist,0x3f,sizeof(dist));
for(int i=1;i<=m;i++)
{
int x=read(),y=read(),z=read();
dist[x][y]=min(dist[x][y],z);
dist[y][x]=min(dist[y][x],z);
}
//Floyd核心代码
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
if(k!=j)
dist[j][k]=min(dist[j][k],dist[j][i]+dist[i][k]);
//i表示断点,j,k是两个端点。
printf("%d\n",dist[1][n]);
n=read(),m=read();
}
return 0;
}
Dijkstra(百科)
这是一个稳定的算法,能计算一个起点到其他所有点的最短路,用了一个贪心的思想,但是它无法处理边权为负的情况,是一个比较优秀,也很常见的算法。时间复杂度Θ(n²)。
例题:POJ2387。
可以发现n已经有1000了,用Floyd肯定会超时,所以用Dijkstra来处理。
Code:
#include<cstdio>
#include<algorithm>
#include<cstring>
#define inf 0x7ffffff
#define N 1005
using namespace std;
int dist[N][N],ans[N];
bool b[N];
int inline read()
{
int f=1,x=0;char s=getchar();
while(s<'0'||s>'9')
{
if(s=='-')f=-1;
s=getchar();
}
while(s<='9'&&s>='0')
{
x=x*10+s-'0';
s=getchar();
}
x*=f;
return x;
}
int main()
{
int t=read(),n=read();
memset(dist,0x3f,sizeof(dist));//n只有1000,可以开邻接矩阵方便读取。
for(int i=1;i<=t;i++)
{
int x=read(),y=read(),z=read();
dist[x][y]=min(dist[x][y],z);
dist[y][x]=min(dist[y][x],z);
}
for(int i=1;i<=n;i++)ans[i]=dist[1][i];//ans[i]表示1到i的最短距离。当求以s为起点的最短路时将1改为s即可
memset(b,false,sizeof(b));//b[i]表示i这个点有没有被访问过
b[1]=true;
for(int i=1;i<=n;i++)
{
int Min=inf,k=0;
for(int j=1;j<=n;j++)//找目前没有访问过的离起点最近的点
if(!b[j]&&ans[j]<Min)
{
Min=ans[j];
k=j;
}
if(k==0)break;//没找到直接跳出循环
b[k]=true;
for(int j=1;j<=n;j++)//以k为中转点松弛与它相连的每一条边
if(!b[j])
ans[j]=min(ans[j],ans[k]+dist[k][j]);
}
printf("%d",ans[n]);
return 0;
}
如果还要追求更短的时间,我们还可以进行堆优化,时间复杂度Θ(E㏒V)(因为我非常菜,只会STL优先队列优化,不会非常牛逼的Fibonacci堆和二叉堆)。
依然是POJ这道题下面用STL优先队列写一遍。
Code:
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N=2005,inf=1e9+7;
struct node
{
int vet,next,len;
}edge[N*2];
int tot,head[N*2],dist[N],vis[N];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >Q;
inline int read()
{
int x=0,f=1;
char s=getchar();
while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
while(s<='9'&&s>='0'){x=x*10+s-'0';s=getchar();}
return x*f;
}
void add(int u,int v,int w)
{
edge[++tot].vet=v;
edge[tot].next=head[u];
head[u]=tot;
edge[tot].len=w;
}
int main()
{
int T=read(),n=read();
memset(head,-1,sizeof(head));
while(T--)
{
int u=read(),v=read(),w=read();
add(u,v,w);add(v,u,w);
}
for(int i=1;i<=n;i++)dist[i]=inf,vis[i]=0;
dist[1]=0;
Q.push(make_pair(dist[1],1));
while(!Q.empty())
{
int u=Q.top().second;
Q.pop();
if(vis[u])continue;
vis[u]=1;
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].vet;
if(dist[u]+edge[i].len<dist[v]&&!vis[v])
{
dist[v]=dist[u]+edge[i].len;
Q.push(make_pair(dist[v],v));
}
}
}
printf("%d\n",dist[n]);
return 0;
}
Bellman-Ford(百科)
这可以说是最没有用的一个算法,因为人们通常会记住它的改进版SPFA。所以这里也不再复述,直接讲SPFA。
SPFA(百科)
SPFA是一个不稳定的算法,是Bellman-Ford的队列优化,唯一比Dijkstra好的地方就是它可以处理负权边,时间复杂度Θ(kE),在稀疏图中k小于2,但稠密图中SPFA复杂度会退化,最大甚至会达到Θ(VE)。但一般来讲还是非常高效的。
随便弄一道题:HDU2066。用SPFA写一遍。
Code:
#include<bits/stdc++.h>
#define INF 0x7fffffff
#define N 1005
using namespace std;
int dist[N][N],b[N],ans[N],Q[N];
inline int read()
{
int f=1,x=0;char s=getchar();
while(s>'9'||s<'0'){if(s=='-')s=-1;s=getchar();}
while(s<='9'&&s>='0'){x=x*10+s-'0';s=getchar();}
return x*f;
}
int main()
{
while(EOF)
{
int T=read(),S=read(),D=read(),n=0;
memset(dist,0x3f,sizeof(dist));
for(int i=1;i<=T;i++)
{
int a=read(),b=read(),time=read();
dist[a][b]=min(dist[a][b],time);
dist[b][a]=min(dist[b][a],time);
n=max(n,a);n=max(n,b);
}
for(int i=1;i<=S;i++)
{
int city=read();
dist[0][city]=0;dist[city][0]=0;
n=max(n,city);
}
memset(ans,0x3f,sizeof(ans));
memset(b,true,sizeof(b));
int head=0;Q[++head]=0;ans[0]=0,b[0]=false;
while(head>0)
{
int k=Q[head--];
b[k]=true;
for(int i=0;i<=n;i++)
if(ans[i]>ans[k]+dist[k][i])
{
ans[i]=ans[k]+dist[k][i];
if(b[i])
{
b[i]=false;
Q[++head]=i;
}
}
}
int lastans=INF;
for(int i=1;i<=D;i++)
{
int want=read();
lastans=min(lastans,ans[want]);
}
printf("%d\n",lastans);
}
return 0;
}
这个代码应该说是最浅显易懂的了,下面再用STL写一遍。
Code:
#include<bits/stdc++.h>
#define INF 0x7fffffff
#define N 1005
using namespace std;
int b[N],ans[N],head[2*N],tot;
queue<int>Q;
struct edge
{
int vet,len,next;
}edge[2*N];
inline int read()
{
int f=1,x=0;char s=getchar();
while(s>'9'||s<'0'){if(s=='-')s=-1;s=getchar();}
while(s<='9'&&s>='0'){x=x*10+s-'0';s=getchar();}
return x*f;
}
void add(int u,int v,int len)
{
edge[++tot].vet=v;
edge[tot].next=head[u];
head[u]=tot;
edge[tot].len=len;
}
int main()
{
int T,S,D;
while(~scanf("%d%d%d",&T,&S,&D))
{
tot=0;
memset(head,-1,sizeof(head));
for(int i=1;i<=T;i++)
{
int a=read(),b=read(),time=read();
add(a,b,time);add(b,a,time);
}
for(int i=1;i<=S;i++)
{
int city=read();
add(0,city,0);add(city,0,0);
}
memset(b,true,sizeof(b));
memset(ans,0x3f,sizeof(ans));
ans[0]=0,b[0]=false;Q.push(0);
while(!Q.empty())
{
int k=Q.front();
Q.pop();
b[k]=true;
for(int i=head[k];i;i=edge[i].next)
{
int v=edge[i].vet;
if(ans[v]>ans[k]+edge[i].len)
{
ans[v]=ans[k]+edge[i].len;
if(b[v])
{
b[v]=false;
Q.push(v);
}
}
}
}
int lastans=INF;
for(int i=1;i<=D;i++)
{
int want=read();
lastans=min(lastans,ans[want]);
}
printf("%d\n",lastans);
}
return 0;
}
据说SPFA算法有两个优化算法 SLF 和 LLL:
SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j) < dist(i),则将j插入队首,否则插入队尾。
LLL:Large Label Last 策略,设队首元素为i,每次弹出时进行判断,队列中所有dist值的平均值为x,若dist(i)>x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出对进行松弛操作。
然后我一脸懵逼。