概要:
- Dijkstra算法
- Bellman-Ford算法
- SPFA算法
- Floyd算法
1、Dijkstra算法用于解决单源最短路径问题,严格讲是无负权图的最短路径问题。
邻接矩阵版
1 const int maxv=1000; 2 const int INF=1000000000; 3 int n,G[maxv][maxv]; 4 int d[maxv]; //起点到各点的最短路径长度 5 bool vis[maxv]={false}; 6 7 void Dijkstra(int s){ //s为起点 8 fill(d,d+maxv,INF); 9 d[s]=0; 10 for(int i=0;i// 循环n次 11 int u=-1,MIN=INF; //u使d[u]最小,MIN存放最小d[u] 12 for(int j=0;j){ 13 if(vis[j]==false && d[j] //未收录的顶点中到起点距离最小者 14 u=j; 15 MIN=d[j]; 16 } 17 } 18 //找不到小于INF的d[u],说明剩下的顶点和起点s不连通 19 if(u==-1) return; 20 vis[u] =true; 21 for(int v=0;v ){ 22 if( G[u][v]!=INF && vis[v]==false && d[u]+G[u][v] < d[v]){ 23 d[v]=d[u]+G[u][v]; 24 } 25 } 26 } 27 }
邻接表版
1 struct Node{ 2 int v,dis; //v为边的目标顶点, dis为边权 3 }; 4 vectorAdj[maxv]; 5 int n,d[maxv]; 6 bool vis[maxv]={0}; 7 8 void Dijkstra(int s){ 9 fill(d,d+maxv,INF); 10 d[s]=0; 11 for(int i=0;i 12 int u=-1,MIN=INF; 13 for(int j=0;j) { ){ 14 if(d[j] false){ 15 u=j; 16 MIN=d[j]; 17 } 18 } 19 if(u==-1) return; 20 vis[u]=true; 21 for(int j=0;j ){ 22 int v=Adj[u][j].v; 23 if(vis[v]==false && d[u] +Adj[u][j].dis <d[v]){ 24 d[v]=d[u]+Adj[u][j].dis; 25 } 26 } 27 } 28 }
若要求输出最短路径,以邻接矩阵为例:
1 const int maxv=1000; 2 const int INF=1000000000; 3 int n,G[maxv][maxv]; 4 int d[maxv]; //起点到各点的最短路径长度 5 bool vis[maxv]={false}; 6 int pre[maxv]; 7 8 void Dijkstra(int s){ //s为起点 9 fill(d,d+maxv,INF); 10 for(int i=0;i11 d[s]=0; 12 for(int i=0;i 循环n次 13 int u=-1,MIN=INF; //u使d[u]最小,MIN存放最小d[u] 14 for(int j=0;j// ){ 15 if(vis[j]==false && d[j]//未收录的顶点中到起点距离最小者 16 u=j; 17 MIN=d[j]; 18 } 19 } 20 //找不到小于INF的d[u],说明剩下的顶点和起点s不连通 21 if(u==-1) return; 22 vis[u] =true; 23 for(int v=0;v ){ 24 if( G[u][v]!=INF && vis[v]==false && d[u]+G[u][v] < d[v]){ 25 d[v]=d[u]+G[u][v]; 26 pre[v]=u; 27 } 28 } 29 } 30 } 31 32 void DFS(int s,int v){ //从终点开始递归 33 if(v==s){ //如果当前已经到达起点,输出起点并返回 34 printf("%d\n",s); 35 } 36 DFS(s,pre[v]); 37 printf("%d\n",v); 38 }
另外还有一种情况,如果某个结点存在多个前驱结点,那上面这种pre数组的方法就不再适用,改成vector即可:
1 const int maxv=1010; 2 const int INF=1000000000; 3 vector<int> pre[maxv]; 4 void Dijkstra(int s){ 5 fill(d,d+maxv,INF); 6 d[s]=0; 7 for(int i=0;i){ 8 int u=-1,MIN=INF; 9 for(int j=0;j){ 10 if(vis[j]==false && d[j]<MIN){ 11 u=j; 12 MIN=d[j]; 13 } 14 } 15 if(u==-1) return; 16 vis[u]=true; 17 for(int v=0;v ){ 18 if(vis[v]==false &&G[u][v]!=INF){ 19 if(d[u]+G[u][v]<d[v]){ 20 d[v]=d[u]+G[u][v]; 21 pre[v].clear(); 22 pre[v].push_back(u); 23 } 24 else if(d[u]+G[u][v]==d[v]){ 25 pre[v].push_back(u); 26 } 27 } 28 } 29 } 30 }
当访问的结点是路径起点st时(边界),此时tempPath里存了整条路径(倒序),这时需要计算第二标尺value的值,并与optValue比较,若更优则更新optValue并把path覆盖。
1 const int maxv=1010; 2 const int INF=1000000000; 3 int optValue; 4 vector<int> path,tempPath; 5 vector<int> pre[maxv]; 6 7 void Dijkstra(int s){ 8 fill(d,d+maxv,INF); 9 d[s]=0; 10 for(int i=0;i){ 11 int u=-1,MIN=INF; 12 for(int j=0;j){ 13 if(vis[j]==false && d[j]<MIN){ 14 u=j; 15 MIN=d[j]; 16 } 17 } 18 if(u==-1) return; 19 vis[u]=true; 20 for(int v=0;v ){ 21 if(vis[v]==false &&G[u][v]!=INF){ 22 if(d[u]+G[u][v]<d[v]){ 23 d[v]=d[u]+G[u][v]; 24 pre[v].clear(); 25 pre[v].push_back(u); 26 } 27 else if(d[u]+G[u][v]==d[v]){ 28 pre[v].push_back(u); 29 } 30 } 31 } 32 } 33 } 34 35 void DFS(int v){ //v为当前访问结点 36 if(v==st){ 37 tempPath.push_back(v); 38 int value; 39 (计算路径的value) 40 if(value优于optValue){ 41 path=tempPath; 42 optValue=value; 43 } 44 tempPath.pop_back(); //将刚加入的结点删除 45 return; 46 } 47 tempPath.push_back(v); 48 for(int i=0;i ){ 49 DFS(pre[v][i]); 50 } 51 tempPath.pop_back(); 52 }
除此之外,还会碰到第二标尺,常见有以下三种:(具体代码见晴神算法笔记,写的很清楚)
- 新增边权(如增加开销)
- 新增点权(如收集到的物资)
- 求最短路径条数
2、Bellman-Ford 算法
算法流程:
(1)初始化:将除起点s外所有顶点的距离数组置无穷大 d[v] = INF, d[s] = 0
(2)迭代:遍历图中的每条边,对边的两个顶点分别进行一次松弛操作,直到没有点能被再次松弛
(3)判断负圈:如果迭代超过V-1次,则存在负圈
算法优点:
(1)可以检测负环,最坏情况下是进行n-1轮操作,若超过n-1轮后还有松弛说明有负环
算法缺点:
(1)在无负环的情况下,效率比Dijkstra低
1 #include2 #include ;i++){ //进行n-1轮 25 flag=false; //标记为假 26 for(int u=0;u3 #include 4 #include 5 #include <set> 6 #include 7 #include //枚举每个边进行松弛 27 for(int j=0;j //松弛操作 28 int v=Adj[u][j].v; 29 int dis=Adj[u][j].dis; 30 if(d[u]+dis<d[v]){ 31 d[v]=d[u]+dis; 32 flag=true; //松弛成功则标记为真 33 } 34 } 35 } 36 if(flag==false){ //优化:若所有的边i的循环中没有松弛成功的,break 37 break; 38 } 39 } 40 //进行n-1轮操作后如果还能松弛,说明存在负环 (可省略) 41 for(int u=0;u ){ 42 for(int j=0;j ){ 43 int v=Adj[u][j].v; 44 int dis=Adj[u][j].dis; 45 if(d[u]+dis<d[v]){ 46 return false; 47 } 48 } 49 } 50 return true; 51 }
3、SPFA算法
SPFA算法是在Bellman-ford算法的基础上进行改进,依据是每次松弛只会影响被松弛结点的相邻结点,于是将那些顶点加入队列,一直松弛到队列为空。
1 //bellman算法的改进SPFA 2 //由于bellman算法的每轮操作都需要操作所有的边,但只有当u的d[u]改变时,才会改变他的邻接点v的d[v]。 3 //设立一个队列保存待优化的节点, 4 //优化时每次取出队首节点u,并用u点当前的最短路径估计值对离开u点所指向的节点v进行松弛操作, 5 //且v点不在当前的队列中,就将v放入队尾,直到队列空 6 7 #include8 #include 42 int v=Adj[u][j].v; 43 int dis=Adj[u][j].dis; 44 if(d[u]+dis<d[v]){ 45 d[v]=d[u]+dis; 46 if(!inq[v]){ //v不在当前队列,v入队 47 Q.push(v); 48 inq[v]=true; 49 num[v]++; 50 if(num[v]>=n) return false; //若v入队次数大于n-1说明有负环 51 } 52 } 53 } 54 55 } 56 }9 #include 10 #include 11 #include <set> 12 #include 13 #include
4、Floyd算法
算法流程:
枚举结点k(1到n),以k为中介点,枚举所有顶点对 i 和 j 进行松弛。
即 if (dis[i][k]+dis[k][j]
1 //floyd算法:解决全源最短路问题 n^3复杂度,n限制约为200以内 2 #include3 #include 顶点k为中介点,枚举所有顶点对i,j 12 for(int i=0;i4 using namespace std; 5 const int INF=1000000000; 6 const int MAXV=200; 7 int n,m;//n为顶点数,m为边数 8 int dis[MAXV][MAXV]; 9 10 void Floyd(){ 11 for(int k=0;k // ){ 13 for(int j=0;j ){ 14 if(dis[i][k]!=INF&&dis[k][j]!=INF&&dis[i][k]+dis[k][j]<dis[i][j]){ 15 dis[i][j]=dis[i][k]+dis[k][j]; 16 } 17 } 18 } 19 } 20 } 21 22 int main(){ 23 int u,v,w; 24 fill(dis[0],dis[0]+MAXV*MAXV,INF); 25 scanf("%d%d",&n,&m); 26 for(int i=0;i ){ 27 dis[i][i]=0;//初始化 28 } 29 for(int i=0;i ){ 30 scanf("%d%d%d",&u,&v,&w); 31 dis[u][v]=w; //有向图输入为例 32 } 33 Floyd(); 34 for(int i=0;i ){ 35 for(int j=0;j ){ 36 printf("%d ",dis[i][j]); 37 } 38 printf("\n"); 39 } 40 return 0; 41 }