前言
最短路径是数据结构-图中的一个经典问题,求解最短路径的问题,有四种算法,这四种算法各有各的不同,分别是:
Floyd算法、Dijkstra算法、Bellman算法以及SPFA算法。
最常用的是前两者,当然你也可以都掌握,毕竟各有各的好处。
1.Floyd-Warshall算法
优点:实现代码极其简便,比较好理解。
缺点:时间复杂度较高,为O(n3),适用于数据复杂度不高的题目
此方法适用于解决多源图的最短路问题,用枚举的方式,遍历比较哪种路径最短。
代码实现如下:
//floyd-warshall算法
for(int k=1; k<=n; k++) { for(int i=1; i<=n; i++) { for(int j=1; j<=n; j++) { if(map[i][j]>map[i][k]+map[k][j]) { map[i][j]=map[i][k]+map[k][j]; } } } }
2.Dijkstra算法
注意:这个算法只适用于无负权值的单元最短路问题,这种算法用到了贪心,和最小生成树的Prim算法有点类似
思路:对每个找到的点进行标记,每标记一个点,就进行一次数据的更新,遍历完所有的点,确定最后的路径即为最短路径,就结束。
时间复杂度:O(n2) 可进行优化,堆/优先队列优化后的时间复杂度:O(nlogn)
代码实现如下:
//Dijkstra算法 #define inf 0x3f3f3f3f for(i=1; i<n; i++) { min=inf; for(j=1; j<=n; j++) //求出当前dis数组中离第一个顶点最短的距离的顶点的下标 { if(book[j]==0 && dis[j]<min) { min=dis[j]; u=j;//记下这个点的下标 } } book[u]=1; for(k=1; k<=n; k++) { if(a[u][k]<inf) { if(dis[k]>dis[u]+a[u][k])//若找到其他途径比从1号顶点直接到目的顶点的距离短,则替换掉 { dis[k]=dis[u]+a[u][k]; } } }
}
3.Bellman-Ford算法 O(NE)
此方法适用于单源最短路径。
优点:可以求出存在负边权情况下的最短路径。
缺点:无法解决存在负权回路的情况。
时间复杂度:O(NE),N是顶点数,E是边数。
算法思想:很简单。一开始认为起点是“标记点”(dis[1] = 0),每一次都枚举所有的边,必然会有一些边,连接着“未标记的点”和“已标记的点”。
因此每次都能用所有的“已标记的点”去修改所有的“未标记的点”,每次循环也必然会有至少一个“未标记的点”变为“已标记的点”。
代码实现如下:
//Bellman-Ford算法
for(int i = 1; i <= n - 1; i++) { for(int j = 1; j <= E; j++) //注意要枚举所有边,不能枚举点 { if(dis[u] + w[j] < dis[v]) //u, v分别是这条边连接的两个点 { dis[v] = dis[u] + w[j] pre[v] = u; } } }
4.SPFA算法 O(KE)
适用于:稀疏图(侧重于对边的处理)。
时间复杂度:O(KE),K是常数,平均值为二,E是边数。
背景:SPFA是Bellman-Ford算法的一种队列实现,减少了不必要的冗余计算。
这个算法简单地说就是队列优化的Bellman-Ford,利用了每个点不会更新次数太多的特点发明的此算法。
SPFA在形式上和广度优先搜索非常类似,不同的是广度优先搜索中的一个点出了队列就不可能重新进入队列,
但是SPFA中的一个点可能在出队列之后再次被放入队列,也就是说一个点修改过其他的点之后,过了一段时间可能会获得更短的路径,
于是再次用来修改其他的点,这样反复进行下去。
优化方法:
1.循环队列(可以降低队列大小)
2.SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j) < dist(i),则将j插入队首,否则插入队尾。
//SLF优化 if(!vis[temp]) { if(dis[q[head + 1]] < dis[temp]) //注意小于号不要写反,否则时间会爆 { tail = (++tail - 1) % qxun + 1; q[tail] = temp; } else { q[head] = temp; if(--head == 0) head = qxun; } vis[temp] = 1; }
3.LLL:Large Label Last 策略,设队首元素为i,每次弹出时进行判断,队列中所有dist值的平均值为x,
若dist(i)>x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出对进行松弛操作。
代码实现如下:
//SPFA算法 #define inf 0x3f3f3f3f
int spfa(int s,int n) { queue<int>q; memset(dis,inf,sizeof(dis)); dis[s]=0; memset(vis,0,sizeof(vis)); memset(c,0,sizeof(c)); q.push(s); vis[s]=1; flag=0; while(!q.empty()) { int x; x=q.front(); q.pop(); vis[x]=0; //队头元素出队,并且消除标记 for(int k=first[x]; k!=0; k=next[k]) //遍历顶点x的邻接表 { int y=v[k]; if(dis[x]+w[k]<dis[y]) { dis[y]=dis[x]+w[k]; //松弛 if(!vis[y]) //顶点y不在队内 { vis[y]=1; //标记 c[y]++; //统计次数 q.push(y); //入队 if(c[y]>n) //超过入队次数上限,说明有负环 return flag=0; } } } } }
参考资料:
1.https://blog.csdn.net/zezzezzez/article/details/70245548
2.https://blog.csdn.net/mashiro_ylb/article/details/78289790
3.https://blog.csdn.net/tianhaobing/article/details/65443049