最短路算法(Floyd、Dijsktra、Bellman-Ford、SPFA)
来源:互联网 发布:淘宝代缴公积金靠谱么 编辑:程序博客网 时间:2024/05/29 16:13
①初始化(任意两边的距离)
②松弛操作
在图论中,最关键的是如何建图。
在最短路算法中,首先要处理数据,在这个时候,要考虑该用那种方式建图。
比较常见的建图方式:邻接链表、邻接矩阵、前向星、链式前向星、十字链表。
对于这五种建图方式,在这里不做详细讨论,只是大概介绍一下优点和缺点。
邻接链表:适合点多的图
邻接矩阵:适合边多的图
链式前向星:适合不带重边的图。除此之外,无论点多还是边多,链式前向星都能表现出很完美的效率。
前向星和十字链表个人用的很少,不做描述。
①Floyd最短路算法
Floyd最短路算法的代码很短,5行就能搞定,但是思想却非常值得学习。
采用动态规划思想:
dp[k][i][j]表示从i到j之间可以经过1~k节点的最短路径。
状态转移方程:dp[k][i][j]=min{dp[k-1][i][j],dp[k-1][i][k]+dp[k-1][k][j]};
对于dp[k][i][j],可以从dp[k-1][i][j]不经过k结点,或者从dp[k-1][i][j]经过k节点,即dp[k-1][i][k]+dp[k-1][k][j];
因为dp[k]只和dp[k-1]有关,所以可以省略dp最外层的一维空间。
(在初始化操作中,需要将dp初始化为无穷大,但是在松弛操作中,又需要避免数据溢出,所以需要选择一个合理的“无穷大”,0x3f3f3f3f是1061109567)
int dp[maxn][maxn];void Floyd(){memset(dp,0x3f3f3f3f,sizeof(dp));for(int k=1;k<=n;k++){for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]);}}}}
下面介绍一下如何打印路径。
用path[i][j]表示j节点的前驱。在更新dp[i][j]最短路径的同时记录更新path[i][j];
(推荐题:hdu1385)
void print(int a,int b){ printf("Path: %d",a); int next=path[a][b]; while(next!=b){ printf("-->%d",next); next=path[next][b]; } if(a!=b) printf("-->%d",b); putchar(10); }
Dijsktra算法是求单源路径最短路问题。
在n^2的效率下计算出源点src到任一点的最短路径,而Floyd算法是在n^3的效率下计算出任意两点的最短距离。
另外一点,在最短路中也需要注意考虑重边的情况,养成好习惯。
下面先给出Dijsktra算法的详细代码并标注解释:
const int INF=0x3f3f3f3f;const int maxn=1010;int map[maxn][maxn];//map[i][j]表示从i到j的距离int dis[maxn];//表示从源点到i点的最短距离bool vis[maxn];//记录该点是否已经访问过int Dijsktra(int src,int des){memset(dis,INF,sizeof(dis));//初始化dis数组dis[src]=0;//源点到本身的距离为0vis[src]=true;//标记源点for(int i=1;i<=n-1;i++){//只需要更新n-1次int pos,min=-INF;for(int j=1;j<=n;j++){if(!vis[j]&&dis[j]<min)min=dis[pos=j];}if(min==INF) return -1;//如果不存在,返回-1vis[pos]=1;for(int j=1;j<=n;j++){//更新dis数组if(!vis[j]&&dis[j]>dis[pos]+map[pos][j])dis[j]=dis[pos]+map[pos][j];}}return dis[des];//返回源点到目的点的最短距离}
应该可以注意到,通俗一点来讲,Dijsktra就是彼此找出一个距离src最近的点pos,以这个点作为中介点更新src到其他所有点的最短距离。
在更新的过程中,已访问的节点做一个标记,这样可以提高效率,源点初始化后不需要再访问,所以更新n-1次即可。
但是在Dijsktra的优点在于,在查找中介点的过程中,需要遍历所有点,效率为o(n),但是如果用二叉堆来优化的话,效率只需要o(logn)。
这里,我们用priority_queue来实现。
如果不用优先队列的话,Dijsktra的效率为O(2|E|+|v|^2),用优先队列查找里源点最近的点时效率为O(log|V|),整体效率为O(2|E|+|v|log|V|)
具体代码如下(Dijsktra+优先队列这种方式也是有必要掌握的。):
struct edge{int to,cost;edge(){}edge(int to,int cost):to(to),cost(cost){}};typedef pair<int,int> P;vector<edge> G[maxn];int dis[maxn][maxn];//dis[i][j]表示i->j的最短距离int a,b,c;void Dijsktra(int s){priority_queue<P, vector<P>, greater<P> > q;//优先队列优化,维护最短路径memset(dis,INF,sizeof(dis));dis[s][s]=0;;q.push(P(0,s));while(!q.empty()){P p=q.top();q.pop();int v=p.second;if(dis[s][v]<p.first) continue;for(int i=0;i<G[v].size();i++){//更新最短路径edge e=G[v][i];if(dis[s][e.to]>dis[s][v]+e.cost){dis[s][e.to]=dis[s][v]+e.cost;q.push(P(dis[s][e.to],e.to));}}}}
③Bellman-Ford算法:
Bellman-Ford算法可以解决帶负环的问题。这也是它相对于上面两个算法最大的优势所在。
对每一条边e[x],如果dis[edge[x].u]>dis[edge[x].v]+edge[x].w,则edge[x].u=edge[x].v+edge[x].w;该操作至多只需要进行n-1次
为了判断图中是否存在负环,即权值之和<0的环路,对于每一条边e[x],如果存在dis[e[x].u]>dis[edge[x].v]+edge[x].w,则图中存在负环,无法求出单源最短路径。
(推荐提:POJ 1860)
const int INF=0x3f3f3f3f;const int maxn=1010;int dis[maxn];int e;void init(){memset(dis,INF,sizeof(dis));e=0;}struct node{int u;int v;int w;}edge[maxn];void addEdge(int u,int v,int w){edge[e].u=u,edge[e].v=v,edge[e].w=w;e++;}void relax(int x){//松弛操作if(dis[edge[x].u]>dis[edge[x].v]+edge[x].w){edge[x].u=edge[x].v+edge[x].w;}}bool Bellman_Ford(int src){dis[src]=0;for(int i=1;i<=n;i++){for(int j=0;j<e;j++){relax(j);//对每一条变进行松弛操作}}for(int i=0;i<e;i++){if(dis[edge[i].u]>dis[edge[i].v]+edge[i].w){return false;//有回路}}return true;//无回路}
④SPFA最短路算法
SPFA其实是Bellman-Ford算法的队列优化。
先取队首元素u,并将其出队,取消标记,将于点u直接相连的所有点进行松弛操作,如果能进行松弛,那么就更新dis数组。
更新结束后,判断该点是否在队列中,如果不在,那么将点入列,然后进行标记。
判断有无负环:如果某个点进入队列的次数超过n次,则存在负环。
下面给出用STL队列实现的算法:
(推荐题:POJ 3259)
const int INF=0x3f3f3f3f;const int maxn=1010;int dis[maxn],head[maxn],inQueue[maxn];bool vis[maxn];int e;void init(){memset(dis,INF,sizeof(dis));memset(head,-1,sizeof(head));memset(vis,0,sizeof(vis));memset(inQueue,0,sizeof(inQueue));e=0;}struct node{//链式前向星建图int v;int w;int next;}edge[maxn];void addEdge(int u,int v,int w){edge[e].v=v,edge[e].w=w,edge[e].next=head[u],head[u]=e;e++;}bool Spfa(int src){dis[src]=0;vis[src]=1;queue<int>Q;Q.push(src);//源点放入队列while(!Q.empty()){int s=Q.front();Q.pop();vis[s]=0;//出队时取消标记inQueue[s]++;if(inQueue[s]>n) return false;//如果一个点入队n次,表明存在负环for(int i=head[s];i!=-1;i=edge[s].next){if(dis[edge[i].v]>dis[s]+edge[i].w){//松弛操作dis[edge[i].v]=dis[s]+edge[i].w;if(!vis[edge[i].v]){vis[edge[i].v]=1;//入队时进行标记Q.push(edge[i].v);}}}}return true;}如果想要快速判断是否存在负环,Dfs深搜的效率会明显较高。
在无负环的情况下,选择Dijsktra最短路算法效率会比较高,SPFA算法的时间不稳定,Bellman-Ford和Floyd算法的效率都比较高。
在有负环的情况下,选择SPFA算法会比较合理一些。
- 最短路算法(Floyd、Dijsktra、Bellman-Ford、SPFA)
- 最短路算法(Floyd、Dijsktra、Bellman-Ford、SPFA)
- HDU-#2544 最短路(Dijkstra、Floyd、Bellman-Ford、SPFA)
- 最短路(SPFA、Dijkstra、Floyd、Bellman-Ford)
- 最短路知识点总结(Dijkstra,Floyd,SPFA,Bellman-Ford)
- 最短路知识点总结(Dijkstra,Floyd,SPFA,Bellman-Ford)
- hdu-2544-最短路-(bellman-ford、dijkstra、floyd、SPFA算法)
- 最短路算法 Dijkstra Bellman-Ford SPFA
- 最短路算法 :Bellman-ford算法 & Dijkstra算法 & floyd算法 & SPFA算法 详解
- 最短路算法 :Bellman-ford算法 & Dijkstra算法 & floyd算法 & SPFA算法 详解
- 最短路算法 :Bellman-ford算法 & Dijkstra算法 & floyd算法 & SPFA算法 详解
- 最短路算法 :Bellman-ford算法 & Dijkstra算法 & floyd算法 & SPFA算法详解&BFS
- 【图论】【最短路径模板+邻接表】【Floyed+Dijsktra+Bellman-Ford+SPFA】【最短路算法对比分析】
- HDU1874(最短路问题:Dijskra+Floyd+Bellman-Ford+SPFA)
- 最短路模板Dijkstra Bellman-Ford Floyd SPFA
- 模板--Floyd Dijkstra Bellman-Ford spfa 四种最短路经典算法
- hdu-2544-最短路(Dijkstra + Dijkstra优先队列 + Bellman-ford + SPFA +Floyd) 纯模板题
- HDU 2544 最短路 floyd djkstra(邻接表,邻接矩阵) spfa bellman-ford 模板题
- Spring Boot——2分钟构建spring web mvc REST风格HelloWorld
- 自动平滑轮播、左右循环view
- Linux共享内存使用常见陷阱与分析
- mac无法充电解决方法
- 网站搜索引擎优化:你需要多个域名?
- 最短路算法(Floyd、Dijsktra、Bellman-Ford、SPFA)
- JS控制图片显示的大小(图片等比例缩放)
- jquery.validate使用攻略 第五步 正则验证
- windows下mongodb安装与使用整理
- 堆排序解析
- 问题记录
- 获取指定字符前/后的字符串
- zoj1586 QS Network(最小生成树)
- 提高 ASP.NET Web 应用性能的 24 种方法和技巧