图论基础之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次

Floyd算法适用于APSP(All Pairs Shortest Paths),是一种动态规划算法,稠密图效果最佳,边权可正可负。此算法简单有效,由于三重循环结构紧凑,对于稠密图,效率要高于执行|V|次Dijkstra算法
优点:容易理解,可以算出任意两个节点之间的最短距离,代码编写简单
缺点:时间复杂度比较高,不适合计算大量数据。
Spfa算法+邻接表
 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 }

 通过不懈的努力终于看懂邻接表了!!

邻接表是图的一种链式存储结构。对图的每个顶点建立一个单链表(n个顶点建立n个单链表),第i个单链表中的结点包含顶点Vi的所有邻接顶点。又称链接表。
补充:
1.在有向图的邻接表中不易找到指向该顶点的弧。
2.在有向图的邻接表中,对每个表头结点,链接的是以该顶点为起始点射入的尾结点,该尾结点为邻接点。
反正很难理解,但是看懂代码是好说了。。。
最短路的Spfa法+邻接表:(首先声明下面所说的邻接点是指左端点相同所对应的边)
 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的邻接点没有。。。但是是先比较再去看有没有邻接点的

posted on 2013-07-30 10:21  ~~碾压机  阅读(1031)  评论(0编辑  收藏  举报