Floyd算法:
复杂度O(n^3)
首先这个算法使用暴力dp来写的,很容易就会TLE。但是这是一个多源最短路算法,可以求出来任意两点之间的最短距离
示例代码:
1 #include <cstdio> 2 #include <iostream> 3 #define INF 0x3f3f3f3f 4 using namespace std; 5 int n,m,t,a,b,c; 6 int f[105][105]; 7 int main() 8 { 9 //有n个端点,m条边 10 cin >> n >> m >> t; 11 //初始化 12 for (int i=1;i<=n;i++) 13 for (int j=1;j<=n;j++) 14 { 15 if (i==j) f[i][j]=0; 16 else f[i][j]=INF; 17 } 18 //输入边 19 for (int i=1;i<=m;i++) 20 { 21 scanf("%d%d%d",&a,&b,&c); 22 f[a][b]=c; 23 } 24 //核心代码 25 for (int k=1;k<=n;k++) 26 for (int i=1;i<=n;i++) 27 for (int j=1;j<=n;j++) 28 f[i][j]=min(f[i][j],f[i][k]+f[k][j]); 29 //运行完算法之后,f[x][y]里面就是x与y之间的最短距离 30 return 0; 31 }
例题:UVA10048
Dijkstra算法:
算法过程:
给你n个点m条边,你要求x到y的最短距离。这个时候你首先用从x为起点的边来处理一遍控制最短距离的数组dis[](数组初始化dis[x]=0,其他都是正无穷)。然后再用距离x最近的点(设为x1),用以x1为起点的边在来处理一遍dis数组。这个时候dis[x1]里面的值就是x->x1的最短距离。原因就是在所有边都是正数的情况下,那么肯定不可能从x通过一个第三者中转导致x->x1的距离变得更短
堆优化迪杰斯特拉优化的哪个地方:没有堆优化的时候我们用的是普通队列,在以x为起点的时候我们对所有与x有关的边都进行一次松弛(比如有x-->1这一条边,我们要去判断dis[1]与dis[x]+w[x][1],w[x][1]是x与1这一条边的长度),所有边进行松弛之后我们要找出来除了dis[x]之外最小的那个dis[y](这个y是我们假设的那个与x相距最小的那个点【同样这个y也是一个未确定过的点,什么叫未确定?比如x就叫确定过的点,因为dis[x]我们已经保证它是最小的了】)
注意了:我们堆优化的就是这个找dis[y]的过程(因为暴力找需要for循环 ,而我们用优先队列了之后就不需要这么麻烦了)
是一个单源最短路算法
优点:
普通算法复杂度:O(v*e),
加堆优化:在核心代码部分,最复杂的是 while 循环和 for 循环嵌套的部分,while 循环最多循环 v 次(v 为顶点个数),for 循环执行次数与边的数目有关,假设顶点数 v 的最大边数是 e。
for 循环中往优先队列中添加删除数据的复杂度为O(log v)。
综合上述两部分,最终 Dijkstra 算法的时间复杂度是O(e·logv)
缺点:不能处理负边权
贝西在田野里,她想回到谷仓,在农夫约翰早上叫醒她挤奶之前尽可能多睡一会儿。贝西需要美容觉,所以她想尽快回来。农夫约翰的田里有N个(2 <= N <= 1000)个界标,唯一的编号是1..地标是谷仓;贝茜整天站在里面的小树林是地标性的N.奶牛在田野里用T (1 <= T <= 2000)在地标之间不同长度的双向牛道。贝西对自己的导航能力不太自信,所以一旦她开始了,她总是沿着一条路线走到底。根据各地标之间的步道,确定贝西返回谷仓的最小距离。可以保证存在这样的路由。 输入*第一行:两个整数:T和N 第2行 . .T+1:每一行都用三个空格分隔的整数来描述一个轨迹。前两个整数是路线经过的地标。第三个整数是步道的长度,范围1..100。 输出*第1行:单个整数,贝茜从地标N到地标1的最小距离。 样例输入 5 5 1 2 20 2 3 30 3 4 20 4 5 20 1 5 100 样例输出 90
加堆优化+代码:
1 //Dijkstra迪杰斯特拉+堆优化(邻接矩阵存图) 2 #include<stdio.h> 3 #include<string.h> 4 #include<iostream> 5 #include<algorithm> 6 #include<queue> 7 #include<vector> 8 using namespace std; 9 #define MAX 0xffffff 10 struct shudui1 11 { 12 int start,value; 13 bool operator < (const shudui1 q)const 14 { 15 return value<q.value; 16 } 17 }str1; 18 struct shudui2 19 { 20 int start,value; 21 }str2; 22 int v[1005]; 23 priority_queue<shudui1>r; //里面的数据默认是从小到大排序,这样就不用通过for循环遍历在每一次找v里面的最小值,可以直接找到最小值,减少代码运行次数 24 int a,s,d,f,g; 25 vector <shudui2>w[2005]; //将每一个起点的值作为数组的标识 26 //例如,1 2 3这一组数据,就是说1连接着2,那就把所有1能到的地方存到这个里面 27 int main() 28 { 29 scanf("%d%d",&a,&s); 30 for(int i=1;i<=s;++i) 31 v[i]=MAX; 32 while(a--) 33 { 34 scanf("%d%d%d",&d,&f,&g); 35 str2.start=f; 36 str2.value=g; 37 w[d].push_back(str2); 38 str2.start=d; 39 str2.value=g; 40 w[f].push_back(str2); 41 } 42 v[s]=0; 43 str1.start=s; 44 str1.value=0; 45 r.push(str1); 46 while(!r.empty()) 47 { 48 int x,y; 49 str1=r.top(); 50 r.pop(); 51 x=str1.start; 52 y=str1.value; 53 if(v[x]<y) continue; 54 //说明在这个点再此之后又入队了 55 //此次出队的并不是s到这个点的最短路, 56 //所以在这次更新前点v所连的点已经更过一次了 57 //所以后面也不会进行松弛操作 58 int len=w[x].size(); 59 for(int i=0;i<len;++i) 60 { 61 str2=w[x][i]; 62 if((v[x]+str2.value<v[str2.start])) 63 { 64 v[str2.start]=v[x]+str2.value; 65 str1.start=str2.start; 66 str1.value=v[str2.start]; 67 r.push(str1); 68 } 69 } 70 } 71 printf("%d\n",v[1]); 72 return 0; 73 }
注意: 如果是多组输入,要清空vector容器呀!!!!!!!!!(wa的我还以为是我的模板错了。。。。)
修正一下上面迪杰斯特拉中一部分代码:
//Dijkstra迪杰斯特拉+堆优化(邻接矩阵存图) #include<stdio.h> #include<string.h> #include<iostream> #include<algorithm> #include<queue> #include<vector> using namespace std; #define MAX 0xffffff struct shudui1 { int start,value; bool operator < (const shudui1 q)const { return value>q.value; //这个要改成大于号 } }str1;
HDU-6290 奢侈的旅行 (Dijkstra+堆优化) 就例如这一道题,你用小于号就会wa
Spay算法(bellman-Ford优化):
算法过程:
给你n个点m条边,你要求x到y的最短距离。这个时候你要枚举每一条边u->v,权值为w,看可不可以通过dis[v]=dis[u]+w来找到dis[v]的更小值(数组初始化dis[x]=0,其他都是正无穷)。在第一轮的松弛之后,得到的是“只经过一条边”到达其余各顶点的最短路径长度。第二轮的时候,得到的是“只经过二条边”到达其余各顶点的最短路径长度。那我们只需要经过n-1轮次的循环就可以了,因为在一个有n个顶点的图中,任意两点之间的距离最多包含n-1边
Bellman-Ford这种写法相当暴力, 直接循环nodeNum次, 每次枚举每一条边, 假如这条边可以用于松弛源点到端点的距离, 则进行松弛. 至于判断负环, 再枚举一遍所有边, 假如存在边仍能用于松弛, 则说明存在负权回路.
SPFA算法的优化体现在:松弛操作必定只会发生在最短路径前导节点松弛成功过的节点上,用一个队列记录松弛过的节点,可以避免了冗余计算。复杂度可以降低到O(kE),k是个比较小的系数(并且在绝大多数的图中,k<=2,然而在一些精心构造的图中可能会上升到很高)
关于SPFA的时间复杂度,不好准确估计,一般认为是 O(kE),k是常数。可处理负边,可判断负环
Bellman-Ford算法代码:POJ 3259 Wormholes
1 #include <stdio.h> 2 3 #include <string.h> 4 5 #define N 505 6 7 #define inf 100000 8 9 int dis[N]; //最小距离 10 11 int n, m; 12 13 struct Cow 14 15 { 16 17 int x;//起点 18 19 int y;//终点 20 21 int w;//权值 22 23 }cow[5300]; 24 25 int Bellman_ford(int m) 26 27 { 28 29 int i, j, k, t; 30 31 int ok; 32 33 for(i=1; i<=n; i++) 34 35 dis[i] = inf; 36 37 dis[1] = 0; 38 39 //对每一个点进行松弛 尽可能的让路径最小 40 41 for(i=1; i<=n-1; i++) 42 43 { 44 45 ok = 1; 46 47 for(j=1; j<=m; j++) 48 49 { 50 51 if(dis[cow[j].x] > dis[cow[j].y] + cow[j].w) 52 53 { 54 55 dis[cow[j].x] = dis[cow[j].y] + cow[j].w; 56 57 ok = 0; 58 59 } 60 61 } 62 63 //如果没有点进行更新 那么跳出即可 可减小时间复杂度 64 65 if(ok) 66 67 break; 68 69 } 70 71 //判断图中是否有负权值存在 72 73 for(j=1; j<=m; j++) 74 75 { 76 77 if(d[cow[j].x] > d[cow[j].y] + cow[j].w) 78 79 return 0; 80 81 } 82 83 return 1; 84 85 } 86 87 88 89 int main() 90 91 { 92 93 int i, j, t, x; 94 95 int a, b, c, w; 96 97 scanf("%d", &t); 98 99 while(t--) 100 101 { 102 103 scanf("%d%d%d", &n, &m, &w); 104 105 memset(edges, 0, sizeof(edges)); 106 107 //因为是无向图 所以需要将所有的添加进去 108 109 for(i=0, j=1; j<=m;j++) 110 111 { 112 113 scanf("%d%d%d", &a, &b, &c); 114 115 i++; 116 117 cow[i].x = a; 118 119 cow[i].y = b; 120 121 cow[i].w = c; 122 123 i++; 124 125 cow[i].x = b; 126 127 cow[i].y = a; 128 129 cow[i].w = c; 130 131 132 133 } 134 135 for(j=1; j<=w; j++) 136 137 { 138 139 scanf("%d%d%d", &a, &b, &c); 140 141 142 143 i++; 144 145 cow[i].x = a ; 146 147 cow[i].y = b; 148 149 cow[i].w = -c; 150 151 152 153 } 154 155 if(Bellman_ford(i)) 156 157 printf("NO\n"); 158 159 else 160 161 printf("YES\n"); 162 163 } 164 165 return 0; 166 167 }
仍以Til the Cows Come Home 为例题:
1 #include<stdio.h> 2 #include<string.h> 3 #include<queue> 4 #include<algorithm> 5 #include<iostream> 6 using namespace std; 7 const int INF =0xfffff; 8 const int MAX =2005; 9 int w[MAX][MAX]; 10 int d[MAX]; 11 int vis[MAX]; 12 queue<int>q; 13 bool spay(int s,int n) //这个可以用来判断有没有负环的存在 14 { 15 d[s]=0; 16 int cnt=0; 17 q.push(s); 18 q.push(cnt); 19 vis[s]=1; 20 while(!q.empty()) 21 { 22 int x=q.front(); 23 q.pop(); 24 cnt=q.front(); 25 q.pop(); 26 vis[x]=0; 27 if(cnt>n) return 0; //这个是用来判断有没有负环,没有负环的情况最多只用查询其n-1个顶点,多于n个那就不正常了 28 for(int i=1;i<n;++i) //本体是从n点到其他点,所以n点不需要再回到n点,可以不遍历 29 { 30 if(d[i]>d[x]+w[x][i]) 31 { 32 d[i]=d[x]+w[x][i]; 33 if(!vis[i]) 34 { 35 q.push(i); 36 q.push(cnt+1); 37 vis[i]=1; 38 } 39 } 40 } 41 } 42 return 1; 43 } 44 int main() 45 { 46 int n,m; 47 scanf("%d%d",&n,&m); 48 memset(w, 0x3f, sizeof(w)); 49 for(int i=1;i<=m;++i) 50 d[i]=INF; 51 while(n--) 52 { 53 int a,s,d; 54 scanf("%d%d%d",&a,&s,&d); 55 if(w[a][s]>d) //防止重边 56 w[a][s]=d; 57 if(w[s][a]>d) 58 w[s][a]=d; 59 } 60 spay(m,m); 61 printf("%d\n",d[1]); 62 return 0; 63 }
链接表代码:
1 #include <stdio.h> 2 #include<string.h> 3 #include <iostream> 4 #define INF 0x3f3f3f3f 5 using namespace std; 6 const int maxn=1005; 7 struct edge 8 { 9 int u,v,w,next; 10 }e[maxn*maxn]; //如果题目上没有说,那就是n*n条边,n是点 11 int head[maxn],cnt; 12 void add_edge(int x,int y,int z){ //链接表 13 e[cnt].u=x; 14 e[cnt].v=y; 15 e[cnt].w=z; 16 e[cnt].next=head[x]; 17 head[x]=cnt++; 18 } 19 int main(){ 20 cnt=0; 21 int x,y,z; 22 while(~scanf("%d%d%d",&x,&y,&z)){ 23 add_edge(x,y,z); 24 } 25 for(int i=0;i<cnt;++i) //打印 26 { 27 printf("%d %d %d\n",e[i].u,e[i].v,e[i].w); 28 } 29 return 0; 30 }