图论算法——最短路系列

最短路经典算法整理

1.弗洛伊德:三重循环

例题:

codevs

1020 孪生蜘蛛

 

时间限制: 1 s
空间限制: 128000 KB
题目等级 : 黄金 Gold
 
 
 
 
题目描述 Description

在G城保卫战中,超级孪生蜘蛛Phantom001和Phantom002作为第三层防卫被派往守护内城南端一带极为隐秘的通道。

根据防护中心的消息,敌方已经有一只特种飞蛾避过第二层防卫,直逼内城南端通道入口。但优秀的蜘蛛已经在每个通道内埋下了坚固的大网,无论飞蛾进入哪个通道,他只有死路一条!(因为他是无法挣脱超级蛛网的)

现在,001和002分别驻扎在某两个通道内。各通道通过内线相通,通过每条内线需要一定的时间。当特种飞蛾被困某处,001或002会迅速赶来把它结果掉(当然是耗时最少的那个)。

001跟002都想尽早的完成任务,他们希望选择在最坏情况下能尽早完成任务的方案。

输入描述 Input Description

第一行为一个整数N (N<=100) 表示通道数目。

接下来若干行每行三个正整数a,b,t 表示通道a,b有内线相连,通过的时间为t。(t<=100)

(输入保证每个通道都直接/间接连通)

输出描述 Output Description

两个不同的整数x1,x2,分别为001,002驻扎的地点。(如果有多解,请输出x1最小的方案,x1相同则输出x2最小的方案)

样例输入 Sample Input

3

1 2 5

2 3 10

3 1 3

样例输出 Sample Output

1 2

数据范围及提示 Data Size & Hint

 1 #include <iostream>
 2 #include <string.h>
 3 #include <stdio.h>
 4 #include <algorithm>
 5 #define inf 0x7fffffff
 6 using namespace std;
 7 int d[110][110],b[110][110],id;
 8 int main (){
 9     int n,a,b1,t;
10     cin>>n;
11     for (int i=0;i<=n;i++)
12       for (int j=0;j<=n;j++)
13         if (i==j) d[i][j]=0;
14         else d[i][j]=inf;
15     while(scanf("%d%d%d",&a,&b1,&t)!=EOF)
16         d[a][b1]=d[b1][a]=t;
17     
18     for (int k=1;k<=n;k++)
19       for (int i=1;i<=n;i++)
20         for (int j=1;j<=n;j++)
21           if (d[i][k]<inf&&d[k][j]<inf)
22             d[i][j]=min(d[i][j],d[i][k]+d[k][j]);//fuluoyide弗洛伊德算法
23     
24     int Min=inf,p,q;
25     for(int i=1;i<=n;i++)//zuihuaiqingkuang最坏情况处理
26         for(int j=1;j<=n;j++)
27         {
28             if(i==j)continue;
29             else
30             { 
31               for(int k=1;k<=n;k++)
32                     b[i][j]=max(b[i][j],min(d[i][k],d[j][k]));
33             }
34             if(Min>b[i][j])Min=b[i][j],p=i,q=j;
35         }
36     cout<<p<<" "<<q;
37     return 0;
38 }

上述算法注意用弗洛伊德处理最坏情况;

2  Dijkstra

 1 设起点为s,dis[v]表示从s到v的最短路径,pre[v]为v的前驱节点,用来输出路径。
 2        a)初始化:dis[v]=∞(v≠s); dis[s]=0; pre[s]=0; 
 3        b)For (i = 1; i <= n ; i++)
 4             1.在没有被访问过的点中找一个顶点u使得dis[u]是最小的。
 5             2.u标记为已确定最短路径
 6             3.For 与u相连的每个未确定最短路径的顶点v
 7               if  (dis[u]+w[u][v] < dis[v]) 
 8                {
 9                   dis[v] = dis[u] + w[u][v];
10                   pre[v] = u;
11                }
12         c)算法结束:dis[v]为s到v的最短距离;pre[v]为v的前驱节点,用来输出路径。

例题:

最小花费

最小花费
【问题描述】
    在n个人中,某些人的银行账号之间可以互相转账。这些人之间转账的手续费各不相同。给定这些人之间转账时需要从转账金额里扣除百分之几的手续费,请问A最少需要多少钱使得转账后B收到100元。
【输入格式】
    第一行输入两个正整数n,m,分别表示总人数和可以互相转账的人的对数。
    以下m行每行输入三个正整数x,y,z,表示标号为x的人和标号为y的人之间互相转账需要扣除z%的手续费 (z<100)。
    最后一行输入两个正整数A,B。数据保证A与B之间可以直接或间接地转账。
【输出格式】
    输出A使得B到账100元最少需要的总费用。精确到小数点后8位。
【输入样例】
    3 3
    1 2 1
    2 3 2
    1 3 3
    1 3
【输出样例】
    103.07153164
【数据规模】
    1<=n<=2000
 1 #include<iostream>
 2 using namespace std;
 3 double a[2001][2001],dis[2001]= {0},minn;
 4 int n,m,i,j,k,x,y,f[2001]= {0};
 5 void init() {
 6     cin>>n>>m;
 7     for(i=1; i<=m; i++) {
 8         scanf("%d%d",&j,&k);
 9         scanf("%lf",&a[j][k]);
10         a[j][k]=(100-a[j][k])/100;
11         a[k][j]=a[j][k];
12     }
13     cin>>x>>y;
14 }
15 void dijkstra(int x) {
16     for(i=1; i<=n; i++)dis[i]=a[x][i];
17     dis[x]=1;
18     f[x]=1;
19     for(i=1; i<=n-1; i++) {
20         minn=0;
21         for(j=1; j<=n; j++)
22             if(f[j]==0&&dis[j]>minn) {
23                 k=j;
24                 minn=dis[j];
25             }
26         f[k]=1;
27         if(k==y)break;
28         for(j=1; j<=n; j++)
29             if(f[j]==0&&dis[k]*a[k][j]>dis[j])dis[j]=dis[k]*a[k][j]
}
31 }
32 int main() {
33     init();
34     dijkstra(x);
35     printf("%0.8lf",100/dis[y]);
36     return 0;
37 }

3 Bellman

此算法性能较差,不经常用;

1 设s为起点,dis[v]即为s到v的最短距离,pre[v]为v前驱。w[j]是边j的长度,且j连接u、v。
2 初始化:dis[s]=0,dis[v]=∞(v≠s),pre[s]=0
3 For (i = 1; i <= n-1; i++)
4 For (j = 1; j <= E; j++)         //注意要枚举所有边,不能枚举点。
5    if (dis[u]+w[j]<dis[v])          //u、v分别是这条边连接的两个点。
6     {
7        dis[v] =dis[u] + w[j];
8        pre[v] = u;
9   }

不做过多介绍;

4 spfa

 1   dis[i]记录从起点s到i的最短路径,w[i][j]记录连接i,j的边的长度。pre[v]记录前趋。
 2     team[1..n]为队列,头指针head,尾指针tail。
 3     布尔数组exist[1..n]记录一个点是否现在存在在队列中。
 4     初始化:dis[s]=0,dis[v]=∞(v≠s),memset(exist,false,sizeof(exist));
 5     起点入队team[1]=s; head=0; tail=1;exist[s]=true;
 6     do
 7     { 
 8     1、头指针向下移一位,取出指向的点u。
 9     2、exist[u]=false;已被取出了队列
10     3、for与u相连的所有点v          //注意不要去枚举所有点,用数组模拟邻接表存储
11               if (dis[v]>dis[u]+w[u][v])
12                  {
13                       dis[v]=dis[u]+w[u][v];
14                       pre[v]=u;
15                      if (!exist[v]) //队列中不存在v点,v入队。
16                        {
17                                     //尾指针下移一位,v入队;
18                                   exist[v]=true;
19                        }
20 21     }
22     while (head < tail);

例题;

香甜的黄油(Sweet Butter)
【问题描述】
农夫John发现做出全威斯康辛州最甜的黄油的方法:糖。把糖放在一片牧场上,他知道N1<=N<=500)只奶牛会过来舔它,这样就能做出能卖好价钱的超甜黄油。当然,他将付出额外的费用在奶牛上。
农夫John很狡猾。像以前的巴甫洛夫,他知道他可以训练这些奶牛,让它们在听到铃声时去一个特定的牧场。他打算将糖放在那里然后下午发出铃声,以至他可以在晚上挤奶。
农夫John知道每只奶牛都在各自喜欢的牧场(一个牧场不一定只有一头牛)。给出各头牛在的牧场和牧场间的路线,找出使所有牛到达的路程和最短的牧场(他将把糖放在那)。
【输入格式】butter.in
第一行: 三个数:奶牛数N,牧场数P2<=P<=800),牧场间道路数C(1<=C<=1450)
第二行到第N+1: 1N头奶牛所在的牧场号。
N+2行到第N+C+1行:每行有三个数:相连的牧场AB,两牧场间距(1<=D<=255),当然,连接是双向的。
【输出格式】butter.out
一行输出奶牛必须行走的最小的距离和.
【输入样例】
3 4 5
2
3
4
1 2 1
1 3 5
2 3 7
2 4 3
3 4 5
样例图形
         P2 
P1 @--1--@ C1
        |
        |
      5  7  3
       |  
        |    C3
      C2 @--5--@
         P3    P4
【输出样例】
8             //说明:放在4号牧场最优

代码;

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 int n,p,c,i,j,x,y,t,min1,head,tail,tot,u;
 6 int a[801][801],b[501],dis[801],num[801],w[801][801],team[1601];
 7 bool exist[801];
 8 int main() {
 9     freopen("butter.in","r",stdin);
10     freopen("butter.out","w",stdout);
11     cin>>n>>p>>c;
12     for(i=1; i<=p; i++) {
13         b[i]=0;
14         num[i]=0;
15         for(j=1; j<=p; j++)
16             w[i][j]=0x7fffffff/3;
17     }
18     for(i=1; i<=n; i++)
19         cin>>b[i];
20     for(i=1; i<=c; i++) {                                  //邻接矩阵存储
21         cin>>x>>y>>t;
22         w[x][y]=t;
23         a[x][++num[x]]=y;
24         a[y][++num[y]]=x;
25         w[y][x]=w[x][y];
26     }
27     min1=0x7fffffff/3;
28     for(i=1; i<=p; i++) {
29         for(j=1; j<=p; j++) dis[j]=0x7fffffff/3;
30         memset(team,0,sizeof(team));                         //队列数组初始化
31         memset(exist,false,sizeof(exist));                   //exist标志初始化
32         dis[i]=0;
33         team[1]=i;
34         head=0;
35         tail=1;
36         exist[i]=true;      //起始点入队
37         do {
38             head++;
39             head=((head-1)%1601)+1;                           //循环队列处理
40             u=team[head];
41             exist[u]=false;
42             for(j=1; j<=num[u]; j++)
43                 if (dis[a[u][j]]>dis[u]+w[u][a[u][j]]) {
44                     dis[a[u][j]]=dis[u]+w[u][a[u][j]];
45                     if (!exist[a[u][j]]) {
46                         tail++;
47                         tail=((tail-1)%1601)+1;
48                         team[tail]=a[u][j];
49                         exist[a[u][j]]=true;
50                     }
51                 }
52         } while(head!=tail);
53         tot=0;
54         for(j=1; j<=n; j++)
55             tot+=dis[b[j]];
56         if (tot<min1) min1=tot;
57     }
58     cout<<min1;
59     return 0;
60 }

5  并查集

普遍代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 using namespace std;
 4 #define maxn 20001
 5 int father[maxn];
 6   int m,n,i,x,y,q;
 7   /*
 8   int find(int x)                  //用非递归的实现
 9   {
10       while (father[x] != x) x= father[x];
11       return x;
12   }
13   */
14   int find(int x)                  //用递归的实现
15    {
16           if (father[x] != x) father[x] = find(father[x]);  //路径压缩
17           return father[x];
18       
19 }
20   void unionn(int r1,int r2)
21    {
22           father[r2] = r1;
23       
24 }
25   
26 int main()
27    {
28           freopen("relation.in","r",stdin);
29           freopen("relation.out","w",stdout);
30           cin >> n >> m;
31           for (i = 1; i <= n; i++)
32                   father[i] = i;           //建立新的集合,其仅有的成员是i
33           for (i = 1; i <= m; i++)
34               {
35                   scanf("%d%d",&x,&y);
36                   int r1 = find(x);
37                   int r2 = find(y);
38                   if (r1 != r2) unionn(r1,r2);
39           
40     }
41           cin >> q;
42           for (i = 1; i <= q; i++)
43               {
44                   scanf("%d%d",&x,&y);;
45                         if (find(x) == find(y)) printf("Yes
")
else printf("No ");   
}
return 0; 50    51 }

经典例题:家谱树 codevs

6 prim  经典代码

算法采用与Dijkstra、Bellman-Ford算法一样的“蓝白点”思想:白点代表已经进入最小生成树的点,蓝点代表未进入最小生成树的点。
算法描述:
以1为起点生成最小生成树,min[v]表示蓝点v与白点相连的最小边权。
MST表示最小生成树的权值之和。
a)初始化:min[v]= ∞(v≠1); min[1]=0;MST=0;
b)for (i = 1; i<= n; i++)
1.寻找min[u]最小的蓝点u。
2.将u标记为白点
3.MST+=min[u]
4.for 与白点u相连的所有蓝点v  
                 if (w[u][v]<min[v]) 
                      min[v]=w[u][v];
c)算法结束: MST即为最小生成树的权值之和

算法分析&思想讲解:
    Prim算法每次循环都将一个蓝点u变为白点,并且此蓝点u与白点相连的最小边权min[u]还是当前所有蓝点中最小的。这样相当于向生成树中添加了n-1次最小的边,最后得到的一定是最小生成树。
  我们通过对下图最小生成树的求解模拟来理解上面的思想。蓝点和虚线代表未进入最小生成树的点、边;白点和实线代表已进入最小生成树的点、边
 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 int g[101][101];              //邻接矩阵
 6 int minn[101];                //minn[i]存放蓝点i与白点相连的最小边权
 7 bool u[101];                  //u[i]=True,表示顶点i还未加入到生成树中
 8                               //u[i]=False,表示顶点i已加入到生成树中 
 9 int n,i,j;
10 int main()
11 {
12     freopen("wire.in","r",stdin);
13     freopen("wire.out","w",stdout);
14     cin >> n;
15     for (i = 1; i <= n; i++)
16         for (j = 1; j <= n; j++)
17             cin >> g[i][j];           
18     memset(minn,0x7f,sizeof(minn));   //初始化为maxint
19     minn[1] = 0;
20     memset(u,1,sizeof(u));            //初始化为True,表示所有顶点为蓝点
21 for (i = 1; i <= n; i++)
22     {
23         int k = 0;
24         for (j = 1; j <= n; j++)     //找一个与白点相连的权值最小的蓝点k
25             if (u[j] && (minn[j] < minn[k]))
26                 k = j;
27         u[k] = false;                    //蓝点k加入生成树,标记为白点
28         for (j = 1; j <= n; j++)         //修改与k相连的所有蓝点
29             if (u[j] && (g[k][j] < minn[j]))
30                  minn[j] = g[k][j]; 
31     }       
32     int total = 0;
33     for (i = 1; i <= n; i++)             //累加权值 
34         total += minn[i];
35     cout << total << endl;
36     return 0;
37 }

7 克鲁斯卡尔

 1 算法描述:
 2 初始化并查集。father[x]=x。
 3 tot=0
 4 将所有边用快排从小到大排序。
 5 计数器 k=0;
 6 for (i=1; i<=M; i++)      //循环所有已从小到大排序的边
 7   if  这是一条u,v不属于同一集合的边(u,v)(因为已经排序,所以必为最小),
 8     begin
 9      ①合并u,v所在的集合,相当于把边(u,v)加入最小生成树。
10      ②tot=tot+W(u,v)
11       ③k++
12       ④如果k=n-1,说明最小生成树已经生成,则break; 
13     end;
14 6. 结束,tot即为最小生成树的总权值之和。*/

经典代码

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 using namespace std;
 5 struct k{
 6     int x,y,v;
 7     
 8 }a[10000];
 9 int fa[1000];
10 int find(int x)
11 {
12     if(fa[x]!=x)
13     {fa[x]=find(fa[x]);
14     return fa[x];
15     }
16     else
17     return x;
18 }
19 void un(int x,int y)
20 {
21     fa[x]=y;
22 }
23 int cmp( k a, k b)      
24 {
25      if (a.v < b.v) return 1;
26         else return 0; 
27 }
28 int main()
29 {
30     int s,m=0;
31     int n;
32     cin>>n;
33     for(int i=1;i<=n;i++)
34      {
35          for(int j=1;j<=n;j++)
36           {
37               cin>>s;
38               if(s!=0)
39                {
40                    m++;
41                    a[m].x=i;
42                    a[m].y=j;
43                    a[m].v=s;
44                }
45           }
46      }
47      for(int i=1;i<=n;i++)
48       {
49           fa[i]=i;
50       }
51       sort(a+1,a+m+1,cmp);
52       int k=0,tot=0;
53      for(int i=1;i<=m;i++)
54       {
55           int r=find(a[i].x);
56           int rr=find(a[i].y);
57           if(r!=rr)
58            {
59                un(r,rr);
60                tot+=a[i].v;
61                k++;
62            }
63         if(k==n-1)
64          {
65              break;
66          }
67       }
68       cout<<tot;
69     return 0;
70 }
原文地址:https://www.cnblogs.com/lyqlyq/p/6736043.html