概要:
- 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;i++){ //循环n次
11 int u=-1,MIN=INF; //u使d[u]最小,MIN存放最小d[u]
12 for(int j=0;j<n;j++){
13 if(vis[j]==false && d[j]<MIN){ //未收录的顶点中到起点距离最小者
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<n;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 vector<Node> Adj[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<n;i++) {
12 int u=-1,MIN=INF;
13 for(int j=0;j<n;j++){
14 if(d[j]<MIN && vis[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<Adj[u].size();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;i<n;i++) pre[i]=i;
11 d[s]=0;
12 for(int i=0;i<n;i++){ //循环n次
13 int u=-1,MIN=INF; //u使d[u]最小,MIN存放最小d[u]
14 for(int j=0;j<n;j++){
15 if(vis[j]==false && d[j]<MIN){ //未收录的顶点中到起点距离最小者
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<n;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<n;i++){
8 int u=-1,MIN=INF;
9 for(int j=0;j<n;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<n;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<n;i++){
11 int u=-1,MIN=INF;
12 for(int j=0;j<n;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<n;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<pre[v].size();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 #include <iostream>
2 #include <algorithm>
3 #include <vector>
4 #include <cstdio>
5 #include <set>
6 #include <cstring>
7 #include <map>
8 #include <queue>
9 using namespace std;
10 const int MAXV=1000;
11 const int INF=1000000000;
12 struct Node{
13 int v,dis; //v为邻接边的目标顶点,dis为邻接边的边权
14 };
15 vector<Node> Adj[MAXV]; //图G的邻接表
16 int n;//顶点数
17 int d[MAXV];//起点到达各点的最短路径长度
18 bool flag;//用于优化
19
20 bool Bellman(int s){
21 fill(d,d+MAXV,INF);
22 d[s]=0;
23
24 for(int i=0;i<n-1;i++){ //进行n-1轮
25 flag=false; //标记为假
26 for(int u=0;u<n;u++){ //枚举每个边进行松弛
27 for(int j=0;j<Adj[u].size();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<n;u++){
42 for(int j=0;j<Adj[u].size();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 #include <iostream>
8 #include <algorithm>
9 #include <vector>
10 #include <cstdio>
11 #include <set>
12 #include <cstring>
13 #include <map>
14 #include <queue>
15 using namespace std;
16 struct Node{
17 int v,dis; //v为邻接边的目标顶点,dis为邻接边的边权
18 };
19 const int MAXV=510;
20 const int INF=1000000000;
21 vector<Node> Adj[MAXV];
22 int n,d[MAXV],num[MAXV];
23 bool inq[MAXV];
24
25 bool SPFA(int s){
26 memset(inq,false,sizeof(inq));
27 memset(num,0,sizeof(num));
28 fill(d,d+MAXV,INF);
29
30 queue<int> Q;
31 Q.push(s);
32 inq[s]=true;
33 num[s]++; //源点入队次数加1
34 d[s]=0;
35 //主体部分
36 while(!Q.empty()){
37 int u=Q.front();//队首顶点编号为u
38 Q.pop();
39 inq[u]=false;//设置u不在队列中
40 //遍历u的所有邻接边v
41 for(int j=0;j<Adj[u].size();j++){
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 }
4、Floyd算法
算法流程:
枚举结点k(1到n),以k为中介点,枚举所有顶点对 i 和 j 进行松弛。
即 if (dis[i][k]+dis[k][j]<dis[i][j] ) dis[i][j]=dis[i][k]+dis[k][j];
1 //floyd算法:解决全源最短路问题 n^3复杂度,n限制约为200以内
2 #include<cstdio>
3 #include<algorithm>
4 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<n;k++){//顶点k为中介点,枚举所有顶点对i,j
12 for(int i=0;i<n;i++){
13 for(int j=0;j<n;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<n;i++){
27 dis[i][i]=0;//初始化
28 }
29 for(int i=0;i<m;i++){
30 scanf("%d%d%d",&u,&v,&w);
31 dis[u][v]=w; //有向图输入为例
32 }
33 Floyd();
34 for(int i=0;i<n;i++){
35 for(int j=0;j<n;j++){
36 printf("%d ",dis[i][j]);
37 }
38 printf("\n");
39 }
40 return 0;
41 }