图论基础之Floyd(弗洛伊德算法&&Spfa算法&&邻接表):畅通工程续 HDU1874&&最短路 HDU2544
畅通工程续 HDU1874
因为数据给的较小,可以用Floyd算法。
1 #include<iostream> 2 #include<stdio.h> 3 #include<algorithm> 4 using namespace std; 5 int main() 6 { 7 int MAX=10000; 8 int a[200][200]; 9 int n,m,i,j,k,x,y,z,s,t; 10 while(scanf("%d %d",&n,&m)!=EOF) 11 { 12 for(i=0;i<n;i++) 13 for(j=0;j<n;j++) 14 a[i][j]=MAX; 15 for(i=0;i<n;i++) 16 a[i][i]=0; 17 for(i=0;i<m;i++) 18 { 19 scanf("%d%d%d",&x,&y,&z); 20 if(a[x][y]>z) 21 { 22 a[x][y]=z;a[y][x]=z; 23 } 24 } 25 for(k=0;k<n;k++) 26 for(i=0;i<n;i++) 27 for(j=i+1;j<n;j++) 28 { 29 if(a[i][j]>a[i][k]+a[k][j]) 30 {a[i][j]=a[i][k]+a[k][j];a[j][i]=a[i][j];} 31 }//核心:三重递归全面求最小路径。 32 scanf("%d%d",&s,&t); 33 if(a[s][t]==MAX) 34 printf("-1\n"); 35 else 36 printf("%d\n",a[s][t]); 37 } 38 return 0; 39 }
刚刚学习又一种方法:Spfa法:先上代码:(貌似这种也只能查已知起点的)
1 #include<iostream> 2 #include<stdio.h> 3 #include<algorithm> 4 #include<queue> 5 #include<string.h> 6 using namespace std; 7 int a[205][205],c[205],vis[205],max1=10005; 8 queue<int> Q; 9 int main() 10 { 11 int n,m,i,j,k,a1,a2,a3,s,t; 12 while(scanf("%d%d",&n,&m)!=EOF) 13 { 14 for(i=0;i<n;i++) 15 for(j=0;j<n;j++) 16 a[i][j]=max1; 17 for(i=0;i<n;i++) 18 a[i][i]=0; 19 while(m--) 20 { 21 scanf("%d%d%d",&a1,&a2,&a3); 22 if(a[a1][a2]>a3) 23 { 24 a[a1][a2]=a3; 25 a[a2][a1]=a3; 26 } 27 } 28 scanf("%d%d",&s,&t); 29 memset(vis,0,sizeof(vis));//这个数组是用来判断是否入队的!!(然而没有这个程序也对,但是会增加时间) 30 for(i=0;i<n;i++) 31 c[i]=max1; 32 c[s]=0; 33 Q.push(s); 34 while(!Q.empty())//因为这里不小心加了个“;”这个伤心半小时!!!! 35 { 36 k=Q.front(); 37 Q.pop(); 38 vis[k]=0; 39 for(i=0;i<n;i++) 40 if(c[i]>c[k]+a[k][i]) 41 { 42 c[i]=c[k]+a[k][i]; 43 if(vis[i]==0) 44 { 45 Q.push(i); 46 vis[i]=1; 47 } 48 } 49 } 50 if(c[t]!=max1) 51 printf("%d\n",c[t]); 52 else 53 printf("-1\n"); 54 } 55 return 0; 56 }
Spfa算法:
我们用数组d记录每个结点的最短路径估计值,而且用邻接表来存储图G。我们采取的方法是松弛(三角形的一条边>其他两条边之和):设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。(此处可以用queue()也可以模拟一个队列)(效率是最高的吧)
首先最短路存在,上述的spfa算法必能求出最小值。(不能存在负环,就是负权回路)
证明:每次将点放入队尾,都是经过松弛操作达到的。换言之,每次的优化将会有某个点v的最短路径估计值d[v]变小。所以算法的执行会使d越来越小。由于我们假定图中不存在负权回路,所以每个结点都有最短路径值。因此,算法不会无限执行下去,随着d值的逐渐变小,直到到达最短路径值时,算法结束,这时的最短路径估计值就是对应结点的最短路径值。(证毕)
判断有无负环:
如果某个点进入队列的次数超过N次则存在负环 (存在负环则无最短路径,如果有负环则会无限松弛,而一个带n个点的图至多松弛n-1次)
1 #include<iostream> 2 #include<algorithm> 3 #include<stdio.h> 4 #include<queue> 5 #include<string.h> 6 using namespace std; 7 queue<int> Q; 8 int vis[10005],b[10005],t,pre[10005]; 9 struct line 10 { 11 int left; 12 int right; 13 int num; 14 int Next; 15 }a[10005]; 16 void add(int left,int right,int num) 17 { 18 a[t].left=left; 19 a[t].right=right; 20 a[t].num=num; 21 a[t].Next=pre[left]; 22 pre[left]=t++; 23 } 24 int main() 25 { 26 int n,m,left,right,num,a1,b1,i,max1,p,k; 27 max1=10000005; 28 while(scanf("%d%d",&n,&m)!=EOF) 29 { 30 memset(pre,-1,sizeof(pre)); 31 t=1; 32 while(m--) 33 { 34 scanf("%d%d%d",&left,&right,&num); 35 if(left!=right) 36 { 37 add(left,right,num); 38 add(right,left,num); 39 } 40 } 41 scanf("%d%d",&a1,&b1); 42 memset(vis,0,sizeof(vis)); 43 for(i=0;i<n;i++) 44 b[i]=max1; 45 b[a1]=0; 46 Q.push(a1); 47 while(!Q.empty()) 48 { 49 p=Q.front(); 50 Q.pop(); 51 vis[p]=0; 52 for(i=pre[p];i+1;i=a[i].Next) 53 { 54 k=a[i].right; 55 if(b[k]>b[p]+a[i].num)//比较的是一个表的左端点,本身,右端点的值 56 { 57 b[k]=b[p]+a[i].num; 58 if(!vis[k]) 59 { 60 Q.push(k); 61 vis[k]=1; 62 } 63 } 64 } 65 } 66 if(b[b1]!=max1) 67 printf("%d\n",b[b1]); 68 else 69 printf("-1\n"); 70 } 71 return 0; 72 }
最短路 HDU2544
和上面的题目类似:先用Floyd算法写着。
1 #include<iostream> 2 #include<stdio.h> 3 #include<algorithm> 4 using namespace std; 5 int MAX=1000005; 6 int main() 7 { 8 int a[105][105]; 9 int n,m,i,j,k,x,y,z; 10 while(scanf("%d %d",&n,&m)!=EOF) 11 { 12 if(m==0&&n==0) 13 break; 14 for(i=1;i<=n;i++) 15 for(j=1;j<=n;j++) 16 a[i][j]=MAX; 17 for(i=1;i<=n;i++) 18 a[i][i]=0; 19 for(i=1;i<=m;i++) 20 { 21 scanf("%d%d%d",&x,&y,&z); 22 if(a[x][y]>z) 23 {a[x][y]=z;a[y][x]=a[x][y];} 24 } 25 for(k=1;k<=n;k++) 26 for(i=1;i<=n;i++) 27 for(j=i+1;j<=n;j++) 28 { 29 if(a[i][j]>a[i][k]+a[k][j]) 30 {a[i][j]=a[i][k]+a[k][j];a[j][i]=a[i][j];} 31 } 32 printf("%d\n",a[1][n]); 33 } 34 return 0; 35 }
再是用Dijkstra算法做的:(都是开个二维数组,赋值都是一样的!)
1 #include<iostream> 2 #include<stdio.h> 3 #include<algorithm> 4 #include<string.h> 5 using namespace std; 6 int a[1005][1005],w[1005]; 7 int main() 8 { 9 int n,m,i,j,p,min1,a1,a2,a3; 10 while(scanf("%d%d",&n,&m)!=EOF) 11 { 12 if(n==0&&m==0) 13 break; 14 for(i=1;i<=n;i++) 15 for(j=1;j<=n;j++) 16 a[i][j]=100000; 17 for(i=1;i<=n;i++) 18 a[i][i]=0; 19 while(m--) 20 { 21 scanf("%d%d%d",&a1,&a2,&a3); 22 if(a[a1][a2]>a3) 23 { 24 a[a1][a2]=a3; 25 a[a2][a1]=a3; 26 } 27 } 28 memset(w,0,sizeof(w)); 29 for(i=1;i<=n;i++) 30 { 31 min1=100000; 32 for(j=1;j<=n;j++) 33 if(!w[j]&&min1>a[1][j]) 34 { 35 p=j; 36 min1=a[1][j]; 37 } 38 w[p]=1;//找个中间点来比较!! 39 for(j=1;j<=n;j++) 40 if(a[1][j]>a[1][p]+a[p][j]&&!w[j]) 41 { 42 a[1][j]=a[1][p]+a[p][j]; 43 a[j][1]=a[1][j]; 44 } 45 } 46 printf("%d\n",a[1][n]); 47 } 48 return 0; 49 }
之后用Spfa法做的:(都是开个二维数组,赋值都是一样的)
1 #include<iostream> 2 #include<stdio.h> 3 #include<algorithm> 4 #include<string.h> 5 #include<queue> 6 using namespace std; 7 int a[1005][1005],c[1005],vis[1005]; 8 int max1=100000005; 9 int main() 10 { 11 int n,m,i,j,a1,a2,a3,k; 12 queue<int> Q; 13 while(scanf("%d%d",&n,&m)!=EOF) 14 { 15 if(n==0&&m==0) 16 break; 17 for(i=1;i<=n;i++) 18 for(j=1;j<=n;j++) 19 a[i][j]=max1; 20 for(i=1;i<=n;i++) 21 a[i][i]=0; 22 while(m--) 23 { 24 scanf("%d%d%d",&a1,&a2,&a3); 25 if(a[a1][a2]>a3) 26 { 27 a[a1][a2]=a3; 28 a[a2][a1]=a3; 29 } 30 } 31 memset(vis,0,sizeof(vis));///***************************(判断是否入队) 32 for(i=1;i<=n;i++) 33 c[i]=max1;//用来记录值的。。。 34 c[1]=0;//起点为0,貌似这个算法也要知道起点 35 Q.push(1); 36 while(!Q.empty()) 37 { 38 k=Q.front(); 39 Q.pop(); 40 vis[k]=0; 41 for(i=1;i<=n;i++) 42 if(c[i]>c[k]+a[k][i]) 43 { 44 c[i]=c[k]+a[k][i]; 45 if(vis[i]==0)//因为c【i】值变小了,他就会对其他的值进行影响,所以把他放到队列里去(如果在里面就不用了) 46 { 47 Q.push(i); 48 vis[i]=1; 49 } 50 } 51 } 52 printf("%d\n",c[n]);///********************************变的是这个地方 53 } 54 return 0; 55 }
通过不懈的努力终于看懂邻接表了!!
1 #include<iostream> 2 #include<algorithm> 3 #include<stdio.h> 4 #include<string.h> 5 #include<queue> 6 using namespace std; 7 int vis[10005],b[10005],pre[10005],t;//pre[]表示是邻接表的指针 8 struct line 9 { 10 int left;//一条边的左端点 11 int right;//一条边的右端点 12 int num;//一条边的长度 13 int Next;//指向下一条边的指针的储存数组(存储邻接点的) 14 }a[10005]; 15 void add(int left,int right,int num) 16 { 17 a[t].left=left; 18 a[t].right=right; 19 a[t].num=num; 20 a[t].Next=pre[left];//记录的是边左端点的指针(指针不为-1说明有临界点(就是left值一样的)) 21 pre[left]=t++; 22 } 23 int main() 24 { 25 int n,m,left,right,num,i,p,k,max1=100000005; 26 queue<int> Q; 27 while(~scanf("%d%d",&n,&m)&&n&&m) 28 { 29 memset(pre,-1,sizeof(pre)); 30 t=1; 31 while(m--) 32 { 33 scanf("%d%d%d",&left,&right,&num); 34 if(left!=right) 35 { 36 add(left,right,num);//这里是无向图,要进行两次入表 37 add(right,left,num); 38 } 39 } 40 memset(vis,0,sizeof(vis)); 41 for(i=1;i<=n;i++) 42 b[i]=max1; 43 b[1]=0; 44 Q.push(1); 45 while(!Q.empty()) 46 { 47 p=Q.front(); 48 Q.pop(); 49 vis[p]=0; 50 for(i=pre[p];i+1;i=a[i].Next)//先更新再判断是否有邻接点(i+1等价于i!=-1) 51 { 52 k=a[i].right;//更新右端点的值 53 if(b[k]>a[i].num+b[p])//此处的b[p]是表示左端点的值 54 { 55 b[k]=a[i].num+b[p];//更新 56 if(!vis[k]) 57 { 58 Q.push(k); 59 vis[k]=1; 60 } 61 } 62 } 63 } 64 printf("%d\n",b[n]); 65 } 66 return 0; 67 }
代码的50行时,我们发现从起点开始(题目中的1)先是看1有没有邻接点。。。
如果没有,看1对应的右端点假设是2有没有邻接点。。。
直到没有。。。
例子:
3 3
1 2 5
1 3 10
2 3 4 O1
那么点1的邻接点有一个(除自身以外) O1-O2-O-O-O
点2的邻接点没有。。。(但是是先比较再去看有没有邻接点的)