刚看完Acwing上面y总的最短路视频,过来写一篇博客总结一下,也希望能帮助到别人
首先安利一波这个网站,里面有很多视频题解,算法模板,还能进行匹配对战
AcWing
AcWing
AcWing
先上一张图,刚用这个画图软件,可能画的比较拙劣
根据图片我们可以知道,最短路问题分为单源最短路和多源最短路。
单源最短路:只有一个出发点。求出来的应该是一个一维数组,保存该点到各点最短距离
多源最短路:有多个出发点,那么求出来应该是一个二维数组,每行表示一个上面的一维数组。
首先说一下多源最短路把。Floyd算是这几个算法里面最简单无脑的方法了,就是三重循环,暴力遍历所有的点。它的实现思想:从任意节点i到任意节点j的最短路径不外乎2种可能,1是直接从i到j,2是从i经过若干个节点k到j。所以,算法假设Dis(i,j)为节点u到节点v的最短路径的距离,对于每一个节点k,算法检查Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立,如果成立,证明从i到k再到j的路径比i直接到j的路径短,便设置Dis(i,j) = Dis(i,k) + Dis(k,j),这样一来,当遍历完所有节点k,Dis(i,j)中记录的便是i到j的最短路径的距离。
下面上这个算法实现的一个基本模板,具体代码要根据具体问题进行更改
for(int k=1;k<=n;k++){//代表中转顶点从1到n
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(dis[i][j]>dis[i][k]+dis[k][j]){
dis[i][j]=dis[i][k]+dis[k][j];
这个算法应该是大部分人刚接触最短路时最先学到的算法。Dijkstra其实是基于贪心的思想,采用了广度优先搜索的策略,以起始点为中心向外层层扩展,直到扩展到终点为止。。
原理:
设G=(V,E)是一个带权有向图,把图中顶点集合V分为两组,第一组为已求出最短路径的顶点集合(用S表示,初始时S中只有一个源点,以后每求得一条最短路径 , 就将加入到集合S中,直到全部顶点都加入到S中,算法就结束了),
第二组为其余未确定最短路径的顶点集合(用U表示),按最短路径的的递增次序依次把第二组中的顶点加入S中。在加入的过程中,总保持从源点v到S中各个顶点的最短路径长度不大于从源点v到U中任何路径的长度。
此外,每个顶点对应一个距离,S中的顶点的距离就是从v到此顶点的最短路径长度,U中的顶点的距离,是从v到此顶点只包括S中的顶点为中间顶点的当前路径的最短长度。
然后下面提供一个比较好用的模板:
#include
using namespace std;
const int N=510;
int dist[N],g[N][N],n,m;
bool st[N];
int dijkstra()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
for(int i=0;i<n-1;i++)
{
int t=-1;
for(int j=1;j<=n;j++)
if(!st[j]&&(t==-1||dist[j]<dist[t]))
t=j;
st[t]=true;
for(int j=1;j<=n;j++)
dist[j]=min(dist[j],dist[t]+g[t][j]);
}
if(dist[n]==0x3f3f3f3f) return -1;
return dist[n];
}
int main()
{
memset(g,0x3f,sizeof g);
cin>>n>>m;
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
g[a][b]=min(g[a][b],c);
}
cout<<dijkstra();
}
优化原理:
优化Dijkstra有两个版本,一个是用优先队列优化,一个是用堆来进行优化。首先我们要分析哪里需要优化,为什么这样优化?
先回答第一个问题:从上面Dijkstra的原理和代码我们可以看出来。我们每次在找最近的点的时候(就是代码中每次给t赋值的这个过程)上面这个过程的代码是O(n^2)的,那么我们可以考虑用一种存储形式,让他每次直接返回一个最小的,就达成了优化。
然后第二个问题:小根堆和优先队列(设置出队顺序)正好符合这个要求。
优化代码:
#include
using namespace std;
typedef pair<int,int>PII;
const int N=1e5+10;
int n,m,h[N],w[N],e[N],ne[N],idx,dist[N];
bool st[N];
void add(int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int dijkstra()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
priority_queue<PII,vector<PII>,greater<PII>>heap;
heap.push({0,1});
while(heap.size())
{
auto t=heap.top();
heap.pop();
int ver=t.second,distance=t.first;
if(st[ver]) continue;
st[ver]=true;
for(int i=h[ver];i!=-1;i=ne[i])
{
int j=e[i];
if(dist[j]>distance+w[i])
{
dist[j]=distance+w[i];
heap.push({dist[j],j});
}
}
}
if(dist[n]==0x3f3f3f) return -1;
return dist[n];
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
cout<<dijkstra()<<endl;
}
这个算法应该算是除了Flyod之外最简单且最容易理解的算法了。
Bellman-Ford算法的优点是可以发现负圈,缺点是时间复杂度比Dijkstra算法高。
算法流程:
(1)初始化:将除起点s外所有顶点的距离数组置无穷大 d[v] = INF, d[s] = 0
(2)迭代:遍历图中的每条边,对边的两个顶点分别进行一次松弛操作,直到没有点能被再次松弛
(3)判断负圈:如果迭代超过V-1次,则存在负圈
根据上面这个流程可以知道,我们只需要把输入数据按边存储,然后依次遍历完就ok了,算是一个比较傻的算法,这里就不多做解释了。
然后这个算法因为是按边进行处理的,所以有一个优势,就是当题目限制了最短路能有几条边的时候,只有这个算法可以使用。下面我们上一道例题 题目代码来源AcWing 853. 有边数限制的最短路
代码如下:
#include
#include
#include
using namespace std;
const int N = 510, M = 10010;
struct Edge
{
int a, b, c;
}edges[M];
int n, m, k;
int dist[N];
int last[N];
void bellman_ford()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < k; i ++ )
{
memcpy(last, dist, sizeof dist);
for (int j = 0; j < m; j ++ )
{
auto e = edges[j];
dist[e.b] = min(dist[e.b], last[e.a] + e.c);
}
}
}
int main()
{
scanf("%d%d%d", &n, &m, &k);
for (int i = 0; i < m; i ++ )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
edges[i] = {a, b, c};
}
bellman_ford();
if (dist[n] > 0x3f3f3f3f / 2) puts("impossible");
else printf("%d\n", dist[n]);
return 0;
}
作者:yxc
链接:https://www.acwing.com/activity/content/code/content/48523/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
SPFA算法就是用优先队列优化后的Bellman—Ford算法,所以原理我们就不在赘述了。
这个算法算是使用最为普遍的,很多问题不管是正权图还是带负权图,都可以用SPFA算法。
优化原理:
Bellman—Ford算法的时间复杂度比较高,原因在于Bellman—Ford算法要递推n次,每次递推,扫描所有的边,在递推n次的过程中很多判断是多余的,SPFA算法是Bellman—Ford算法的一种队列实现,减少了不必要的冗余判断。
大致流程:
用一个队列来进行维护。初始时将源点加入队列。每次从队列中取出一个顶点,并与所有与它相邻的顶点进行松弛,若某个相邻的顶点松弛成功,则将其入队。重复这样的过程直到队列为空时算法结束。
代码模板:
#include
#include
#include
#include
using namespace std;
const int N = 100010;
int n, m;
int h[N], w[N], e[N], ne[N], idx;
int dist[N];
bool st[N];
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);
dist[1] = 0;
queue<int> q;
q.push(1);
st[1] = true;
while (q.size())
{
int t = q.front();
q.pop();
st[t] = 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;
}
}
}
}
return dist[n];
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
int t = spfa();
if (t == 0x3f3f3f3f) puts("impossible");
else printf("%d\n", t);
return 0;
}
作者:yxc
链接:https://www.acwing.com/activity/content/code/content/48498/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。