20200418图论算法总结

Floyed-Warshall算法O(N^3)
弗洛伊德算法是最简单的最短路径算法,可以计算任意两点之间的最短路径,适用于出现负边权的情况。

for(k=1;k<=n;k++)
    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
           if(dis[i][j]>dis[i][k]+dis[k][j])
               dis[i][j]=dis[i][k]+dis[k][j];
               //或者dis[i][j]=dis[i][j]||(dis[i][k]&&dis[k][j]);

1342:【例4-1】最短路径问题
【题目描述】
平面上有n个点(n≤100),每个点的坐标均在-10000~10000之间。其中的一些点之间有连线。

若有连线,则表示可从一个点到达另一个点,即两点间有通路,通路的距离为两点间的直线距离。现在的任务是找出从一点到另一点之间的最短路径。

【输入】
共n+m+3行,其中:

第一行为整数n。

第2行到第n+1行(共n行) ,每行两个整数x和y,描述了一个点的坐标。

第n+2行为一个整数m,表示图中连线的个数。

此后的m 行,每行描述一条连线,由两个整数i和j组成,表示第i个点和第j个点之间有连线。

最后一行:两个整数s和t,分别表示源点和目标点。

【输出】
一行,一个实数(保留两位小数),表示从s到t的最短路径长度。

【输入样例】
5
0 0

2 0
2 2
0 2
3 1
5
1 2
1 3
1 4
2 5
3 5
1 5
【输出样例】
3.41

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
using namespace std;
int n,m,s,t,x,y;
int a[101][3];
double f[101][101];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
      cin>>a[i][1]>>a[i][2];
    cin>>m;

    memset(f,0x7f,sizeof(f)); //先初始化为一个较大内容的数组
for(int i=1;i<=m;i++)
    {
        cin>>x>>y;
        f[x][y]=f[y][x]=sqrt(pow(double(a[x][1]-a[y][1]),2)+pow(double(a[x][2]-a[y][2]),2));
    }
    cin>>s>>t;

    for(int k=1;k<=n;k++)
       for(int i=1;i<=n;i++)
          for(int j=1;j<=n;j++)
             if(i!=j&&i!=k&&j!=k&&f[i][j]>f[i][k]+f[k][j])
                f[i][j]=f[i][k]+f[k][j];

    printf("%.2lf",f[s][t]);
    return 0;     
    
    
    
}

1343:【例4-2】牛的旅行
【题目描述】
农民John的农场里有很多牧区。有的路径连接一些特定的牧区。一片所有连通的牧区称为一个牧场。但是就目前而言,你能看到至少有两个牧区不连通。现在,John想在农场里添加一条路径 ( 注意,恰好一条 )。对这条路径有这样的限制:一个牧场的直径就是牧场中最远的两个牧区的距离 ( 本题中所提到的所有距离指的都是最短的距离 )。考虑如下的两个牧场,图1是有5个牧区的牧场,牧区用“*”表示,路径用直线表示。每一个牧区都有自己的坐标:

在这里插入图片描述

图1所示的牧场的直径大约是12.07106, 最远的两个牧区是A和E,它们之间的最短路径是A-B-E。

这两个牧场都在John的农场上。John将会在两个牧场中各选一个牧区,然后用一条路径连起来,使得连通后这个新的更大的牧场有最小的直径。注意,如果两条路径中途相交,我们不认为它们是连通的。只有两条路径在同一个牧区相交,我们才认为它们是连通的。

现在请你编程找出一条连接两个不同牧场的路径,使得连上这条路径后,这个更大的新牧场有最小的直径。

【输入】
第 1 行:一个整数N (1 ≤ N ≤ 150), 表示牧区数;

第 2 到 N+1 行:每行两个整数X,Y ( 0 ≤ X,Y≤ 100000 ), 表示N个牧区的坐标。每个牧区的坐标都是不一样的。

第 N+2 行到第 2*N+1 行:每行包括N个数字 ( 0或1 ) 表示一个对称邻接矩阵。

例如,题目描述中的两个牧场的矩阵描述如下:

A B C D E F G H
A 0 1 0 0 0 0 0 0
B 1 0 1 1 1 0 0 0
C 0 1 0 0 1 0 0 0
D 0 1 0 0 1 0 0 0
E 0 1 1 1 0 0 0 0
F 0 0 0 0 0 0 1 0
G 0 0 0 0 0 1 0 1
H 0 0 0 0 0 0 1 0
输入数据中至少包括两个不连通的牧区。

【输出】
只有一行,包括一个实数,表示所求答案。数字保留六位小数。

【输入样例】
8
10 10
15 10
20 10
15 15
20 15
30 15
25 10
30 10
01000000
10111000
01001000
01001000
01110000
00000010
00000101
00000010【输出样例】
22.071068

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
using namespace std;
int n;
char ch;
int a[151][3];
double f[151][151],maxt=1e12,minx=1e20,temp,m[151];
double dist(int x,int y)
{
    return sqrt(pow(double(a[x][1]-a[y][1]),2)+pow(double(a[x][2]-a[y][2]),2));
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
      cin>>a[i][1]>>a[i][2];
    for(int i=1;i<=n;i++)
      for(int j=1;j<=n;j++)
      {
          cin>>ch;
          if(ch=='1')
          f[i][j]=dist(i,j);
          else
          f[i][j]=maxt;
      }
    
    for(int k=1;k<=n;k++)
      for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        if(i!=k&&i!=j&&j!=k)
        if(f[i][k]<maxt-1&&f[k][j]<maxt-1)  //确保连通 
        if(f[i][j]>f[i][k]+f[k][j])
            f[i][j]=f[i][k]+f[k][j];
    
    for(int i=1;i<=n;i++)
      for(int j=1;j<=n;j++)
      if(f[i][j]<maxt-1&&m[i]<f[i][j]) m[i]=f[i][j];  //(1) 
      
    for(int i=1;i<=n;i++)
      for(int j=1;j<=n;j++)
      if(i!=j&&f[i][j]>maxt-1)
      {
          temp=dist(i,j);
          if(minx>m[i]+m[j]+temp)
          minx=m[i]+m[j]+temp;
      }  //(4)
      
    for(int i=1;i<=n;i++)
      if(m[i]>minx)  //(2)(5)
      minx=m[i];
    
    printf("%.6lf",minx);
    return 0;     
    
    
    
}

Dijkstra算法O(N^2)
用于计算一个点到其他所有点的最短路径的算法,是一种单源最短路径算法,也就是说,只能计算起点只有一个的情况

#include<iostream>
#include<fstream>
#include<string>
using  namespace std;
#define MaxSize  10
#define MAXCOST 10000
// 图的结构
template<class T>
struct Graph
{
    T vertex[MaxSize];// 存放图中顶点的数组
    int arc[MaxSize][MaxSize];// 存放图中边的数组
    int vertexNum, arcNum;// 图中顶点数和边数
};
// 最短路径Dijkstra算法
void Dijkstra(Graph<string> G,int v)
{
    int dist[MaxSize];//  i到j的路径长度
    string path[MaxSize];// 路径的串
    int s[MaxSize];// 已找到最短路径的点的集合
    bool Final[MaxSize];//Final[w]=1表示求得顶点V0至Vw的最短路径
    // 初始化dist\path
    for (int i = 0; i < G.vertexNum; i++)
    {
        Final[i] = false;
        dist[i] = G.arc[v][i];
        if (dist[i] != MAXCOST)
            path[i] = G.vertex[v] + G.vertex[i];
        else
            path[i] = " ";        
    }
    s[0] = v; // 初始化s
    Final[v] = true;
    int num = 1;
    while (num < G.vertexNum)
    {
        // 在dist中查找最小值元素
        int k = 0,min= MAXCOST;
        for (int i = 0; i < G.vertexNum; i++)
        {
            if (i == v)continue;
            if (!Final[i] && dist[i] < min)
            {
                k = i;
                min = dist[i];
            }                
        }
        cout << dist[k]<<path[k]<<endl;
        s[num++] = k;// 将新生成的结点加入集合s
        Final[k] = true;
        // 修改dist和path数组
        for (int i = 0; i < G.vertexNum; i++)
        {
            if (!Final[i]&&dist[i] > dist[k] + G.arc[k][i])
            {
                dist[i] = dist[k] + G.arc[k][i];
                path[i] = path[k] + G.vertex[i];
            }
        }
    }
}
int main()
{
    // 新建图
    Graph<string> G;
    string temp[]= { "v0","v1","v2","v3","v4" };
    /*int length = sizeof(temp) / sizeof(temp[0]);
    G.vertexNum = length;
    G.arcNum = 7;*/
    ifstream in("input.txt");
    in >> G.vertexNum >> G.arcNum;
    // 初始化图的顶点信息
    for (int i = 0; i < G.vertexNum; i++)
    {
        G.vertex[i] = temp[i];
    }
    //初始化图G的边权值
    for (int i =0; i <G.vertexNum; i++)
    {
        for (int j = 0; j <G.vertexNum; j++)
        {
            G.arc[i][j] = MAXCOST;
        }
    }
    for (int i = 0; i < G.arcNum; i++)
    {
        int m, n,cost;
        in >> m >> n >> cost;
        G.arc[m][n] = cost;
    }
    Dijkstra(G, 0);
    system("pause");
    return 0;
}

1344:【例4-4】最小花费
【题目描述】
在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

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
using namespace std;
double a[2001][2001],dis[2001],minn;
int f[2001],n,m,k,x,y;
void read()
{
    int xx,yy,zz;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&xx,&yy);
        scanf("%lf",&a[xx][yy]);
        a[xx][yy]=(100-a[xx][yy])/100;
        a[yy][xx]=a[xx][yy];
            }
    cin>>x>>y;
}
void dijkstra(int x)
{
    for(int i=1;i<=n;i++)
    dis[i]=a[x][i];
    dis[x]=1;
    f[x]=1;
    for(int i=1;i<=n-1;i++)
    {
        minn=0;
        for(int j=1;j<=n;j++)
          if(f[j]==0&&dis[j]>minn)
          {
              k=j;
              minn=dis[j];
          }
        f[k]=1;
        if(k==y) break;
        for(int j=1;j<=n;j++)
         if(f[j]==0&&dis[k]*a[k][j]>dis[j])
         dis[j]=dis[k]*a[k][j];
    }
}
int main()
{
    read();
    dijkstra(x);
    printf("%.8lf",100/dis[y]);
    return 0;     

}

Bellman-Ford算法O(NE) //N是顶点数,E是边数
同样是用来计算从一个点到其他所有点的最短路径的算法,能够处理存在负边权问题的情况,单无法处理存在负权回路的情况。
初始化:

dis[s]=0,dis[v]=0x3f3f3f3f(v!=s),pre[s]=0;
for(i=1;i<=n-1;i++)
    for(j=1;j<=E;j++)
        if(dis[u]+w[i]<dis[v]) //u,v分别是这条边连接的两个点
        {
            dis[v]=dis[u]+w[j];
            pre[v]=u;
        }

SPFA算法O(kE)(k是常数,E是边数)
SPFA算法是Bellman-Ford算法的一种队列实现,减少了不必要的冗余计算。
dis[i]记录从起点s到i的最短路径,w[i][j]记录连接i,j的边的长度,pre[v]记录前趋,team[1]=s,head=0,tail=1,exist[s]=true;

do
{
    1.头指针向下移一位,取出指向的点u
    2.布尔数组exist[n]=false,已被取出队列
    3.for与u相连的所有点v
        if(dis[v]>dis[u]+w[u][v];
        {
            dis[v]=dis[u]+w[u][v];
            pre[v]=u;
            if(!exist[v])    //队列中不存在v点,v入列
            {
                尾指针下移一位,v入列
                exist[v]=true;
            }
        }
    }
    while(head<tail)

1345:【例4-6】香甜的黄油
【题目描述】
农夫John发现做出全威斯康辛州最甜的黄油的方法:糖。把糖放在一片牧场上,他知道N(1≤N≤500)只奶牛会过来舔它,这样就能做出能卖好价钱的超甜黄油。当然,他将付出额外的费用在奶牛上。

农夫John很狡猾。像以前的巴甫洛夫,他知道他可以训练这些奶牛,让它们在听到铃声时去一个特定的牧场。他打算将糖放在那里然后下午发出铃声,以至他可以在晚上挤奶。

农夫John知道每只奶牛都在各自喜欢的牧场(一个牧场不一定只有一头牛)。给出各头牛在的牧场和牧场间的路线,找出使所有牛到达的路程和最短的牧场(他将把糖放在那)。

【输入】
第一行: 三个数:奶牛数N,牧场数P(2≤P≤800),牧场间道路数C(1≤C≤1450)。

第二行到第N+1行: 1到N头奶牛所在的牧场号。

第N+2行到第N+C+1行:每行有三个数:相连的牧场A、B,两牧场间距(1≤D≤255),当然,连接是双向的。

【输出】
一行 输出奶牛必须行走的最小的距离和。

【输入样例】
3 4 5
2
3
4
1 2 1
1 3 5
2 3 7
2 4 3
3 4 5
【输出样例】
8
【提示】
说明:放在4号牧场最优。
在这里插入图片描述

 #include<bits/stdc++.h>
using namespace std;
const int N=10005;
const int inf=0x3f3f3f3f;
int n,p,c;
int head[N];
int s[N];
int cnt;
int vis[N];
int dis[N];
struct node
{
 int next;
 int from;
 int to;
 int dis;
}edge[N*2];

void add_edge(int from,int to,int dis)
{
 edge[++cnt].next=head[from];
 edge[cnt].to=to;
 edge[cnt].dis=dis;
 head[from]=cnt;
}

void spfa(int x)
{
 memset(vis,0,sizeof(vis));
 memset(dis,inf,sizeof(dis));

 queue<int>Q;
 Q.push(x);
 vis[x]=1;
 dis[x]=0;
 while(!Q.empty()){
     int u=Q.front();
     Q.pop();
     vis[u]=0;
     for(int i=head[u];i!=0;i=edge[i].next){
         int to=edge[i].to;
         int di=edge[i].dis;
         if(dis[to]>dis[u]+di){
             dis[to]=dis[u]+di;
             if(!vis[to]){
                 vis[to]=1;
                 Q.push(to);
             }
         }
     }

 }
}
int main()
{
 ios::sync_with_stdio(false);
 cin>>n>>p>>c;
 for(int i=1;i<=n;i++){
     cin>>s[i];
 }

 memset(head,0,sizeof(head));
 int a,b,cc;
 for(int i=1;i<=c;i++){
     cin>>a>>b>>cc;
     add_edge(a,b,cc);
     add_edge(b,a,cc);
 }

 int minn=inf;
 int sum=0;
 for(int i=1;i<=p;i++){
     spfa(i);
     sum=0;
     for(int j=1;j<=n;j++){
         sum+=dis[s[j]];
     }
     if(minn>sum) minn=sum;
 }
 cout<<minn<<endl;



 return 0;
}
原创文章 25 获赞 38 访问量 841

猜你喜欢

转载自blog.csdn.net/weixin_46434074/article/details/105597725