最短路径
图论中可以算最重要的算法之一。可以解决很多问题。
分为很多种算法
1.floyed(图中每对顶点(任意两点)之间的最短路径)
2.dijkstra(图中一个顶点到其他顶点的最短路径)
3.bellman—ford(求单源点到其他点的最短距离,可判断是否有负环)
4.SPFA(对bellman—ford的改进)
三角形迭代
求最小路的基础
if (d[i][k]+d[k][j]<d[i][j] )
d[i][j]=d[i][k]+d[k][j]
1.floyed
概念
枚举每个点k,再枚举每两个点【i,j】。用k对【i,j】迭代,求最短路。
代码
void floyed()
{
//初始化条件:
a[i][j]=0 //自己到自己为0;对角线为0;
a[i][j]=边权,i与j有直接相连的边
a[i][j]= +∞ ,i与j无直接相连的边。
// 一般设为: memset 10 即可;
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(a[i][j]>a[i][k]+a[k][j])
a[i][j]=a[i][k]+a[k][j];
}
图示
输出路径
用二维数组存储两点之间的路过点。
void dfs(int i,int j)
{
if (i==j)
cout<<i<<' ';
else
if(path[i][j]>0)
{
dfs(i,path[i][j]);
cout<<j<<' ';
}
}
void floyed_path(int st,int ed)
{
//初始化:
path[i][j]=i; path[j][i]=j;
for (int k=1;k<=n;k++)
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (d[i][k]+d[k][j]<d[i][j] )
{
d[i][j]=d[i][k]+d[k][j];
path[i][j]=path[k][j]
}//存储路径
dfs(st,ed);
}
2.dijkstra
概念
每次找距离单源点最近的点,以它为结点,进行对其他所有点的迭代,更行他们目前到源点的最短路径的算法
代码
void dijkstra(int s)
{
memset(dis,10,sizeof(dis));
memset(f,0,sizeof(f));
dis[s]=0;
f[s]=1;
for(int i=1;i<=n;i++)
dis[i]=a[s][i];
for(int i=1;i<n;i++)
{
int minn=dis[0],c=0;
for(int j=1;j<=n;j++)
if(!f[j]&&dis[j]<minn)
{
minn=dis[j];
c=j;
}
if (c==0) break;
f[c]=1;
for(int j=1;j<=n;j++)
if (!f[j]&&dis[c]+a[c][j]<dis[j])
dis[j]=dis[c]+a[c][j];
}
}
图示
】
输出路径
void dfs(int i,int j)
{
if (i==j)
cout<<i<<' ';
else
if (path[j]>0)
{
dfs(i,path[j]);
cout<<j<<' ';
}
}//其实同floyed。
void dijkstra(int s,int e)
{
int path[maxn]={};
memset(dis,10,sizeof(dis));
memset(f,0,sizeof(f));
dis[s]=0;
f[s]=1;
for(int i=1;i<=n;i++)
{
dis[i]=a[s][i];
path[i]=s;//有边存顶点
}
for(int i=1;i<n;i++)
{
int minn=dis[0],c=0;
for(int j=1;j<=n;j++)
if(!f[j]&&dis[j]<minn)
{
minn=dis[j];
c=j;
}
if (c==0) break;
f[c]=1;
for(int j=1;j<=n;j++)
if (!f[j]&&dis[c]+a[c][j]<dis[j])
{
dis[j]=dis[c]+a[c][j];
path[j]=c;
}
}
dfs(s,e); //搜结尾
}
3.bellman—ford
概念
就是取每条边,每次用边起点x迭代终点y。(无向图中也要用y迭代x)
重复上述操作,直至没有边可以进行迭代,或已重复N次操作,就退出。
若迭代了N次,说明有负环!!!
代码
bool bellman-ford(int s)
{
memset(dis,10,sizeof(dis));//初始化
dis[s]=0;
for(int i=1;i<n;i++)
{
bool flag=0;
for(int i=1;i<=t;i++)
{
if (dis[a[i].x]+a[i].v<dis[a[i].y])
{
flag=1;
dis[a[i].y]=dis[a[i].x]+a[i].v;
}
}
if(!flag) return 0;//没有迭代,退出。
}
return 1;//有负环
}
图示
4.spfa
概念
bellman-ford算法可以判断负权回路,可以求单源点最短路。但枚举N次,每次枚举所有边,那么他的效率还是比较低的。
其实对于一条边【i,j】,若上次dis【i】没有改变,那么就不需要对这条边进行处理(因为处理了也没有变化)
所以SPFA对bellman—ford进行了改进,用队列存储所有被迭代过的点,每次处理队头,并用他来迭代所有与他连边的点,使被迭代的点入队。
同时!!!队头是要出队的(因为他下次可能会被其他点又迭代了)
代码
void spfa(int s)
{
memset(dis,10,sizeof(dis));
int q[maxn]={};
bool vis[maxn]={};
dis[s]=0;
vis[s]=1;//初始化
q[1]=s;
int head=0,tail=1;
while (++head<=tail)
{
int ts=q[head];
vis[ts]=0;//出队;
for(int i=linkk[ts];i;i=a[i].next)
{
int te=a[i].y;
if (dis[ts]+a[i].v<dis[te])
{
dis[te]=a[i].v+dis[ts];
if (!vis[te])
{
vis[te]=1;
q[++tail]=te;
}
}
}
}
}
图示
总结
比较
1.floyed
时间复杂度为O(N^3);
可以求任两点间距离;
比较稳定;
代码最简单;
2.dijkstra
时间复杂度为O(N^2);
单源点最短路;
也较稳定;
代码较简单;
负权回路不适用;
可用堆优化。(蒟蒻萝卜并不会。。百度大佬博客)
3.bellman—ford
时间复杂度O(V^3);
便于求负权回路;
效率较慢;
4.spfa
时间复杂度不好算,但平均较低;
采用更新队列优化bellman—ford;
性价比较高。