NOIP模板复习(3) 最短路三巨头Floyd,Dijkstra与SPFA
NOIP模板复习(3) 最短路三巨头Floyd,Dijkstra与SPFA
最短路作为图论中的重要问题是noip的常考点,这里就写一下求最短路的常见的三种算法
目录
1.Floyd算法
Floyd算法是一种基于动态规划思想的多源最短路算法能算出图中们每个节点的之间的最短路
1.1算法原理
首先我们可以定义\(f[i][j]\)意为节点i到j之间的最短路径(有点类似于邻接矩阵)。然后就可以得到状态转移方程\(f[i][j]=min(f[i][j],f[i][k],f[k][j])\),其原理就是不断通过枚举两点之间的中间的点来确定它们的最短路径。
这个算法代码非常容易实现但时间复杂度十分之高,达到了\(O(n^3)\)的级别,不过它可以在某些题中起到预处理的作用,比如noip2016的换教室。
1.2算法实现
代码如下:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
int dis[1005][1005];//floyd算法常使用临接矩阵实现
void floyd(int n)
{
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
}
}
return ;
}
int main()
{
memset(dis,0x3f,sizeof(dis));
int n,m;
scanf("%d %d",&n,&m);
register int u,v,w;
for(int i=1;i<=m;i++)
{
scanf("%d %d %d",&u,&v,&w);
dis[u][v]=w;
dis[v][u]=w;
}
floyd(n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cout<<i<<" "<<j<<":"<<dis[i][j]<<endl;
}
}
return 0;
}
2.Dijkstra算法
Dijkstra算法是荷兰计算机科学家dijkstra在据说是喝咖啡的时候发明的(%%%),是一种单源最短路算法能求出起点到所有点的最短路。
2.1算法原理
Dijkstra算法的思想就是利用已经求出的最短路径来更新未知的路径。具体做法就是先找到一个距源点最近的点,这样得到就一定是源点到该点最小的距离。再以该点为中间点尝试更新源点到其它节点的距离。如此重复这样所有点的最短距都能求出来了。
该算法的朴素实现的时间复杂度为\(O(n^2)\)(n为节点数量),但我们可以将朴素写法中的找到距源点最近的点用堆这种数据结构来实现,这样时间复杂度便为\(O((n+m)logn)\)(m为边的数量),近似于\(O(nlogn)\)算是一种很高效的算法。
2.2算法实现
这里为使用了堆优化后代码:
#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <algorithm>
using namespace std;
struct node
{
int to,val;
};
struct point
{
int id,val;
bool operator <(const point& mid)const
{
return this->val>mid.val;
}
};
vector<node> q[1005];
priority_queue<point> p;
int dis[1005];
bool vised[1005];
void dijkstra(int start)
{
memset(dis,0x3f,sizeof(dis));
memset(vised,0,sizeof(vised));
dis[start]=0;
p.push((point){start,0});
while(!p.empty())
{
int now=p.top().id;
p.pop();
if(vised[now])
{
continue;
}
vised[now]=1;
int len=q[now].size();
for(int i=0;i<len;i++)
{
node next=q[now][i];
if(dis[now]+next.val<dis[next.to])
{
dis[next.to]=dis[now]+next.val;
p.push((point){next.to,dis[next.to]});
}
}
}
return ;
}
int main()
{
int n,m;
scanf("%d %d",&n,&m);
register int u,v,w;
for(int i=1;i<=m;i++)
{
scanf("%d %d %d",&u,&v,&w);
q[u].push_back((node){v,w});
q[v].push_back((node){u,w});
}
dijkstra(1);
for(int i=1;i<=n;i++)
{
cout<<1<<" "<<i<<":"<<dis[i]<<endl;
}
return 0;
}
3.SPFA算法
SPFA算法是Shortest Path Faster Algorithm的缩写,顾名思义是用于求最短路的算法,由西南交通大学的段凡丁发现。其实是Bellman-Ford算法的队列优化。它有着正常情况下较优的时间复杂度,最重要的是它还能够处理有负权回路的情况。
3.1算法原理
该算法的原理是动态逼近法:维护一个队列不断的从其中拿出节点进行松弛操作,同时将经过松弛操作的节点放进队列里。这样不断的逼近,最后总能得到最短路径。
该算法的时间复杂度为\(O(em)\),其中\(e\)为边的数量,m为平均每个点进队的次数。段凡丁在论文中证明m通常小于2,但实际上该算法在实际应用中十分不稳定,时间复杂度时高时低。
3.2算法实现
代码如下:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <cmath>
#include <queue>
#include <cstdlib>
#include <algorithm>
using namespace std;
struct node
{
int to,val;
};
vector<node> q[1005];
queue<int> p;
int dis[1005];
bool vised[1005];
void SPFA(int start)
{
memset(dis,0x3f,sizeof(dis));
memset(vised,0,sizeof(vised));
vised[start]=1;
p.push(start);
dis[start]=0;
while(!p.empty())
{
int now=p.front();
p.pop();
vised[now]=0;
int len=q[now].size();
for(int i=0;i<len;i++)
{
node next=q[now][i];
if(dis[now]+next.val<dis[next.to])
{
dis[next.to]=dis[now]+next.val;
if(!vised[next.to])
{
vised[next.to]=1;
p.push(next.to);
}
}
}
}
return ;
}
int main()
{
int n,m;
scanf("%d %d",&n,&m);
register int u,v,w;
for(int i=1;i<=m;i++)
{
scanf("%d %d %d",&u,&v,&w);
q[u].push_back((node){v,w});
q[v].push_back((node){u,w});
}
SPFA(1);
for(int i=1;i<=n;i++)
{
cout<<1<<" "<<i<<":"<<dis[i]<<endl;
}
return 0;
}
4.总结
总的来说,因为SPFA的复杂不稳定,所以在求最短路的时候通常是用堆优化的Dijkstra算法,SPFA更多是用来判断负权回路。至于Floyd算法则只用在一些预处理上了。
Over!