图论算法->最短路

求最短路算法,有Floyd,dijkstra,Bellmanford,spfa等诸多高级算法。优化方法也是层出不穷。

我们不妨分析一下各算法的使用特点(可能不准确

1.Floyd算法 复杂度O(n³)可计算任意两点间最短路径 可处理负边权情况

2.Dijkstra算法 复杂度O(n²)只能计算单源最短路径 不可处理负边权情况(多源可再加一重循环)

3.Bellman-Ford算法 复杂度O(边数*点数) 计算单源最短路径 能处理负边权情况但无法处理存在负权回路情况

4.spfa算法 复杂度O(边数*玄学常数) 单源最短路径 可处理负边权情况

另外,这种算法在稀疏图上是好,而在稠密图有不乐观的一面

另外,还有输出最短路径方案,记录前驱。参见玛丽卡一题。

我们不妨先来讨论两种图的存储方式

第一种是邻接矩阵,开了一个二维数组。如f[i][j]记录了i到j有一条边,且权值为f[i][j]

第二种是邻接表。用一个结构体+head数组记录这条边的信息

 1 struct node{
 2     int val,to,next;
 3     //node存储第i条边的信息,val为第i条边的权值,to为第i条边的终
 4     //点,next为同一起点的下一条边的第几条边数 
 5 }edge[2005]; 
 6 int head[2005];//head[x]表示以x节点为起点的第一条边的边数 
 7 int tot;//tot记录已经读到第几条边 
 8 
 9 /*建边*/
10 void build(int x,int y,int z)
11 {
12     tot++;
13     edge[tot].val=z;
14     edge[tot].to=y;
15     edge[tot].next=head[x];//这里好像链表一样 
16     head[x]=tot;
17 } 

我们可以用这张图帮助理解。

长得有点像链表 是不是耶...

一、最暴力的弗洛伊德Floyd

 1 //floyd 
 2 #include<bits/stdc++.h>
 3 using namespace std;
 4 const int inf=0x7f7f7f7f;
 5 int n,m;
 6 int w[2000][2000];
 7 int dis[2000][2000];
 8 int main()
 9 {
10     scanf("%d%d",&n,&m);
11     for(int i=1;i<=n;i++)
12     {
13         for(int j=1;j<=n;j++)
14          dis[i][j]=inf;
15     }
16     for(int i=1;i<=m;i++)
17     {
18         int u,v;
19         scanf("%d%d%d",&u,&v,&w[u][v]);
20         dis[u][v]=w[u][v];
21     }
22     //floyd
23     for(int k=1;k<=n;k++)
24      for(int i=1;i<=n;i++)
25       for(int j=1;j<=n;j++)
26        if(dis[i][j]>dis)
27         dis[i][j]=dis[i][k]+dis[k][j];
28     //输出 dis[i][j]为i到j的最短路 
29     return 0;
30 }

二、Dijkstra+堆优化+邻接表

 1 // Dijkstra
 2 #include<bits/stdc++.h>
 3 using namespace std;
 4 int n,m,s;
 5 const int inf=0x3f3f3f3f;
 6 int tot;
 7 bool visit[500005];
 8 int head[500005],dis[500005];
 9 struct node{
10     int val,to,next;
11 }edge[500005];
12 priority_queue<  pair<int,int>  > q;
13 //大根堆 pair第一维为dis相反数以达到小根堆效果,第二维为节点编号 
14 void add(int x,int y,int z)
15 {
16     tot++;
17     edge[tot].to=y;
18     edge[tot].val=z;
19     edge[tot].next=head[x];
20     head[x]=tot;
21 }
22 void dijkstra()
23 {
24     for(int i=1;i<=n;i++)
25      dis[i]=inf;
26     dis[s]=0;//这里默认把1作为起点,求多源时可另加调整
27     q.push(make_pair(0,s));
28     while(!q.empty())
29     {
30         int x=q.top().second;
31         q.pop();//pop会把二元组中两个元素一并弹出 
32         if(visit[x]) continue;
33         visit[x]=1;
34         for(int i=head[x];i;i=edge[i].next)
35         {
36             int y=edge[i].to;
37             int z=edge[i].val;
38             if(dis[y]>dis[x]+z&&visit[y]==0)
39             {
40                 dis[y]=dis[x]+z;
41                 q.push(make_pair(-dis[y],y));
42             }
43          } 
44     }
45 }
46 int main()
47 {
48     scanf("%d%d%d",&n,&m,&s);
49     for(int i=1;i<=m;i++)
50     {
51         int x=0,y=0,z=0;
52         scanf("%d%d%d",&x,&y,&z);
53         add(x,y,z);
54     }
55     dijkstra();
56     for(int i=1;i<=n;i++)
57      printf("%d ",dis[i]);
58     return 0;
59 }

三、SPFA算法(大力拒绝bellmanford

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cmath>
 5 #include<queue>
 6 using namespace std;
 7 const int inf=2147483647;
 8 int n,m,s;
 9 int num;
10 int pre[20000];
11 struct node{
12     int to,val,next;
13 }edge[500005];
14 int dis[20000];
15 bool visit[20000];
16 void add(int x,int y,int z)
17 {
18     num++;
19     edge[num].val=z;
20     edge[num].to=y;
21     edge[num].next=pre[x];
22     pre[x]=num;
23 }
24 void spfa()
25 {
26     queue<int>q;
27     for(int i=1;i<=n;i++)
28     {
29         dis[i]=inf;
30     }
31     q.push(s);
32     dis[s]=0;
33     visit[s]=1;
34     while(!q.empty())
35     {
36         int u=q.front();
37         q.pop();
38         visit[u]=0;
39         for(int i=pre[u];i>0;i=edge[i].next)
40         {
41             int v=edge[i].to;
42             if(dis[v]>dis[u]+edge[i].val)
43             {
44                 dis[v]=dis[u]+edge[i].val;
45                 if(visit[v]==0)
46                 {
47                     visit[v]=1;
48                     q.push(v);
49                 }
50             }
51             
52         }
53     }
54 }
55 int main()
56 {
57     scanf("%d%d%d",&n,&m,&s);
58     for(int i=1;i<=m;i++)
59     {
60         int x=0,y=0,z=0;
61         scanf("%d%d%d",&x,&y,&z);
62         add(x,y,z);
63     }
64     spfa();
65     for(int i=1;i<=n;i++)
66     {
67         if(s==i) cout<<"0"<<" ";
68         else cout<<dis[i]<<" ";
69     }
70     
71     
72     return 0;
73 }

最后我们再来讨论一下防止溢出的问题。

建邻接表的时候,edge开到边数,无向图开二倍边;

head dis visit均存到点数即可。

原文地址:https://www.cnblogs.com/nopartyfoucaodong/p/8677257.html