最短路径
没有用的话qaq :
Ummmm…图论的大部分知识本来早就有学过,只是一直没有写成博文来梳理,但既然上了qbxt DP图论就写一篇来总结下,主要是来听DP的,但…由于太菜的原因,DP听得天花乱坠QWQ
图中的最短路算法分为单源最短路算法(dijkstra,Bellman-food,spfa)和多源最短路算法(floyd),单源最短路算法是求图上一点到其他任意一点的最短路径,多源最短路算法是求图上任意两点之间的最短路。
一,多源最短路算法
Floyd弗洛伊德算法,运用了动态规划的思想,用邻接矩阵存储,期初将从任何一点到其他所有点的最短路都置成正无穷,然后输入时修改有的权值,其主要思想是动态规划,在最外围枚举点,看看有没有一个点可以更新两点之间的最短路。
代码简单易懂
memset(dis,0x3f,sizeof(dis));
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]);
时间复杂度为O(n^3 ),空间复杂度为O(n^2),时间和空间均不是很优,一般较少采用,一般在求n范围较小的多源最短路题中才有出现。
可以适用于有负权边的情况,可以判断图的连通性,可以传递闭包
二,单源最短路算法
1,Dijkstra
a.将所有点分为两个集合,最短路确定的集合S 和最短路未确定的集合T,初始S ={s}
b.求T 中每个点v 的当前最短路dv = min<u,v>∈E,u∈S{du + wu,v}
c.取出T 中dv 最小的点,其最短路一定就是dv,将其加入S
d.不断重复上面的操作,直到所有点的最短路都确定
与普利姆的思想基本相同,代码实现也基本相同,同样朴素写法时间复杂度较劣,可以采用堆优化至O((N + M)logN)
inline void dijsktra(int s)
{
for(int i=1;i<=n;i++)
dist[i]=INF,vis[i]=false;
heap.push(make_pair(0,s));
vis[s]=true;
while(!heap.empty())
{
int p=heap.top().second;
heap.pop();
if(vis[p]) continue;
vis[p]=true;
for(int i=first[p];i;i=edge[i].nxt)
{
int e=edge[i].ed,d=edge[i].len;
if(dist[e]>dist[p]+d)
{
dist[e]=dist[p]+d;
heap.push(make_pair(-dist[e],e));
}
}
}
}
Dijikstra算法是图论最短路算法中最高效的算法,但注意其不适用于含有负权边的情况。
3,Bellmanfood,SPFA
其实是一个东西啦,SPFA是对Bellfood的优化,所以在这里就只整理SPFA了,比起Dijkstra,SPFA的用途更为广泛,其适用于有负权边的情况,可以判断一个图里是否有负环,但硬伤是效率,极容易被卡。
我们先看Bellmanfood的算法流程:
a.初始令ds = 0,其余di =∞
b. 依次使用每一条边< u, v > 更新,dv min{dv , du + wu,v}
c.不断循环直到所有di 都不会更新
d.因为任何一条最短路包含至多n − 1 条边,没有负环则不超过n 轮就会停止
SPFA考虑使用队列优化Bellman-Ford 算法,如果更新了du,则将u入队。每次取队首u 更新其邻点v 的dv。因为Bellmanfood会遍历一些不可能完成松弛操作的情况。
代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
struct node
{
int ed,len,nxt;
};
node edge[2333];
int n,m,first[2333],dis[2333],cnt;
bool vis[2333];
queue <int> q;
inline void add_edge(int s,int e,int d)
{
cnt++;
edge[cnt].ed=e;
edge[cnt].len=d;
edge[cnt].nxt=first[s];
first[s]=cnt;
}
inline void spfa(int st)
{
memset(dis,0x3f,sizeof(dis));
memset(vis,false,sizeof(dis));
q.push(st); vis[st]=true;
while(!q.empty())
{
int p=q.front(); vis[p]=false;
for(int i=first[p];i;i=edge[i].nxt)
{
int e=edge[i].ed;
int d=edge[i].len;
int newd=dis[p]+d;
if(newd<dis[e])
{
dis[e]=newd;
if(!vis[e])
{
q.push(e);
vis[e]=true;
}
}
}
}
return;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int s,e,d;
scanf("%d%d%d",&s,&e,&d);
add_edge(s,e,d);
add_edge(e,s,d);
}
spfa(1);
return 0;
}//随手一写,不保证编译能过 Orz QwQ
SPFA判负环,就要有一个点的进队次数大于等于N,那么我们就认为有负环,因为负环会产生最短路为无限小,因此它会在那不停的转,有时候对于比较稠密的图,可能会卡SPFA判负环,所以对于比较稠密的图把队列换成栈可以更快的找出负环。
队列实现代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
struct node
{
int ed,len,nxt;
};
node edge[2333];
int n,m,first[2333],inq[2333],dis[2333],cnt;
bool vis[2333];
queue <int> q;
inline void add_edge(int s,int e,int d)
{
cnt++;
edge[cnt].ed=e;
edge[cnt].len=d;
edge[cnt].nxt=first[s];
first[s]=cnt;
}
inline bool spfa(int st)
{
memset(dis,0x3f,sizeof(dis));
memset(vis,false,sizeof(dis));
q.push(st); vis[st]=true; inq[st]++;
while(!q.empty())
{
int p=q.front(); vis[p]=false;
for(int i=first[p];i;i=edge[i].nxt)
{
int e=edge[i].ed;
int d=edge[i].len;
int newd=dis[p]+d;
if(newd<dis[e])
{
dis[e]=newd;
if(!vis[e])
{
q.push(e);
inq[e]++;
vis[e]=true;
if(inq[e]>=n) return true;
}
}
}
}
return false;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int s,e,d;
scanf("%d%d%d",&s,&e,&d);
add_edge(s,e,d);
add_edge(e,s,d);
}
spfa(1);
return 0;
}
写在最后,Yousiki Orz Orz