zoukankan      html  css  js  c++  java
  • 图论4-floyd

    大家想一想,spfa是从bfs演化过来的,dijkstra是贪心思想,由此可见,这些“高级”的最短路算法都是有基础算法演化得来的。

    而我今天要说的算法就是由基础的动态规划演化出来的最短路算法-floyd

    还有用一到题来开启今天的内容:GF和猫咪的玩具

    题意分析:有n个圆环,将两个圆环用力拉可以将这两个圆环之间的绳索拉直(当然是最短的路径,因为要把更长的路径拉直最短的路径

    就会被扯断),如果有两条长度一样的路径,只会拉直其中一条,问抓住两个圆环用力拉最多能拉直多少绳索。抽象一下题意,就是有

    一个图包括n个点和m个长度为1的连接点的边,问任意两点间的最短路径长度的最大值是多少。

    思路解析:如果要做这道题,当然可以做n遍spfa或dijkstra,但是那样的话写起来会很费劲,所以,我要隆重推荐这个算任意两点间最

    短路的神器:floyd!这个算法是从动态规划中演化出来的,所以就用dp四步走来讲一下这个最短路算法。

    1.状态:dis[i][j]表示从i到j的最短路。

    2.初值:dis[i][j]=INF,对于每条路A—>B,dis[A][B]=1;

    3.转移:dis[i][j]=min(dis[i][j],dis[i][k],dis[k][j]){1<=i,j,k<=n};

    4.答案:看是什么题了,比如这道题就是max(dis[i][j]){1<=i,j<=n}

    最后看一下这种算法的时间复杂度:转移中枚举状态是n^2,而转移是所以整个的时间复杂度是O(n^3),能通过这道题。

    用途:一般用于稠密图任意点间的最短路计算。

    所以代码就是这样的吗?

    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            for(int k=1;k<=n;k++)
                dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);

    看起来没什么问题,但是一做发现答案完全不对啊,可是这不是完全按照四步走写出来的代码吗?的确是这样,但我们还忽略了一个问题,

    那就是dp的先后顺序。必须要把前面的求完才能用前面的转移后面的,也就是说我们的dis[i][j]的含义还有一些问题,那就是没有规定它的

    中转点k,也就是说dis[i][j]的真正含义是当枚举到k时,从i到j经历的中转点为1~k时i到j的最短路径长度。

    所以真正的正确的floyd代码是这样的,如果还有不明白的看注释

    int dis[NR][NR]; //dis[i][j]表示从考虑中转点到k(其中k是在循环里面的)时i到j的最短路径 
    for(int k=1;k<=n;k++)// 枚举中转点 
        for(int i=1;i<=n;i++)//枚举起点 
            for(int j=1;j<=n;j++)//枚举终点 
                dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);//状态转移方程
    //转移方程成立的原因:如果从i绕到k之后再绕到j比直接i到j更短,当然要选择短的那一种 

    最后,上这道题的完整代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int NR=1e2+10;
    const int INF=0x3f3f3f3f;
    int n,m;
    int dis[NR][NR]; //dis[i][j]表示从考虑中转点到k(其中k是在循环里面的)时i到j的最短路径 
    int read()//这道题不能用快读read否则有东西读不进去 
    {
        int x=0,f=1;char ch=getchar();
        while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
        while(ch<='9'&&ch>='0'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
        return x*f;
    }
    int main()
    {
    //    freopen("1.in","r",stdin);
    //    freopen("1.out","w",stdout);
        memset(dis,0x3f,sizeof(dis));//dis初值 
        scanf("%d%d",&n,&m);//点数和边数 
        for(int i=1;i<=n;i++) dis[i][i]=0;//从自己到自己当然长度为0 
        for(int i=1;i<=m;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);//读入x到y有边, 
            dis[x][y]=dis[y][x]=1;//从x到y和从y到x都有长度为1的边 
        }
        for(int k=1;k<=n;k++)// 枚举中转点 
            for(int i=1;i<=n;i++)//枚举起点 
                for(int j=1;j<=n;j++)//枚举终点 
                    dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);//状态转移方程
        //转移方程成立的原因:如果从i绕到k之后再绕到j比直接i到j更短,当然要选择短的那一种 
        int ans=0;
        for(int i=1;i<=n;i++)//统计答案时枚举起点 
            for(int j=i+1;j<=n;j++)//统计答案时枚举终点 
                if(dis[i][j]!=INF) ans=max(ans,dis[i][j]);//更新答案 
        printf("%d",ans);//输出答案 
        return 0;
    }

    你以为floyd只能做多源最短路吗?如果这样你就错了,它还能做一种叫做传递闭包的题。

    先搞清楚概念:传递闭包指给定若干个元素已经若干个关系,要求这种关系具有可传递性,即AξB,BξC,则AξC,例如>,<就是可传递的。

    建立邻接矩阵d,其中dp[i][j]=1/0表示i与j有/没有关系,特别的d[i][i]始终为1,使用floyd来解决传递闭包类问题。

    代码如下:

    bool d[NR][NR];
    int n,m;
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++) d[i][i]=1;
        for(int i=1;i<=m;i++)
        {
        	int x,y;
        	scanf("%d%d",&x,&y);
        	d[x][y]=d[y][x]=1;
    	}
    	for(int k=1;k<=n;k++)
    	  for(int i=1;i<=n;i++)
    		for(int j=1;j<=n;j++)
    		  d[i][j]|=d[i][k]&d[k][j];
    }    
    

    最后,给大家推荐一道传递闭包的题,那就是 POJ1094 Sorting It All Out

  • 相关阅读:
    eclipse中编译调试skynet
    饭否Android端更新流程分析
    ThinkPHP Logic层
    Skynet框架(1)
    ThinkPHP Mongo驱动update方法支持upsert参数
    [转载]yum与apt命令比较
    chkconfig在ubuntu里用什么命令替代
    通过aptget下载下来到/var/cache/apt/archives里的东西能删除吗?
    mysql远程访问的bindaddress设置
    对MySQL性能影响关系紧密的五大配置参数
  • 原文地址:https://www.cnblogs.com/chen-1/p/12574538.html
Copyright © 2011-2022 走看看