Floyd算法相关

前言

这是NOIP最后冲刺阶段写的复习性质的博客。

简介

这篇博客包含了Floyd算法的内容。

主要包括:

  • Floyd求多源最短路

  • Floyd求无向图最小正环

复习性质的博客。

Floyd求多源最短路

简介

这个东西求最短路的方式比较神奇。进行完一次 O(n3) 的算法以后,你就可以得到一个存储这结果的数组 d ,那么d[i][j]就是 i j的最短路长度。

这个算法同时支持有向图和无向图。

原理

首先枚举一个中间节点 k ,然后枚举起始点i和终点 j ,比较d[i][k]+d[k][j] d[i][j] 的大小,把较小的一个的值赋给 d[i][j] 。即:

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

注意,在初始状态下, d 数组的处理方式:

  • 如果ij可达:

    • 有向边:有一条边从 i j

    • 无向边: 有一条边连接 i j

    那么, d[i][j] 的值就是这条边的长度。

    • 否则,它的长度就是无穷大(以下简称 INF )。

    • 正确性解释

      你打眼一看,这个东西似乎有说不通的地方:

      我们在一开始的时候, d 数组的元素几乎都是INF,但是它却是作为更新的标准,这样不会导致更新的内容无效嘛?

      如果你只是站在前几个 k 的取值上看,是的,这样做确实是无效的。

      但是你注意看,这样的操作我们进行了n次,我们寻找了 n 次中间点。而且你会发现:我们在进行操作的时候,其实是三维操作(ijk),而操作的对象 d 则是一个二维的对象(ij)。因此我们可以相信它有能力将先前无效的操作抵消掉,变成有效的操作。因为每次选取一个新的中间点,这之中必定会有有效操作,而随着 k 的值越来越大,有效操作的个数也会越来越多。最终,所有操作均为有效操作。

      当然,这只是一个非常笼统的解释。如果你想要更科学的解释,你往下看:

      最短路径中的Floyd算法(弗洛伊德算法)的较为严格的冥想证明过程

      作者:李均宇(李恒星) 2015.10.31

      仍用数学归纳法,假设:

      • Nn时,弗法正确。

      • N=n+1 时,假设最新一点最后一点为 K ,此时K=n+1

      • 三重循环中,我们都把 K 排在循环中的最后一位。

        现在我们要证明的是,加上新点K点后,经过弗法的三重循环,原来的 n 点之间仍是最短距离,但是n点与 K 点之间的短离是不是最短的就不知的。

        如果原来的n点的某两点之间最短距是与 K 点无关的,显然经过三重循环后,就是最短距了。

        如果原来的n点的某两点之间最短距是经过 K 点的,假设P1, P2 , P3 ,······, Pk1 , Pk , Pk+1 ,······, Pm 本应是实边最短距,不是虚边最短距。

        那么由弗法知, P1 , P2 , P3 ,······, Pk1 Pk+1 ,······, Pm 已是连通的最短路了。且 Pk1Pk PKPK+1 是原始实边,不是虚边。

        经过最外层最后一次循环的松驰操作,必能连通 P1 , P2 , P3 ,······, Pk1 , Pk , Pk+1 ,······, Pm

        所以得证:加上新点 K 点后,经过弗法的三重循环,原来的n点之间仍是最短距离,但是 n 点与K点之间的短离是不是最短的就不知的。

        由于对称性,将 K 点置入内部,把P1点放到最后一点,原来的循环结果不会变的,

        所以三重循环后, K 点与原来的点(除P1外)的最短距,就可以求出来了。

        由于对称性,将 K 点置入内部,把P2点放到最后一点,原来的循环结果不会变的,所以三重循环后, K 点与原来的点(除P2外,但 P1 不除外)的最短距,就可以求出来了。

        所以 K 点与原来的n个点的最短距,也就已经求出来的了,仍是原来的三重循环也。

        这样,弗法就可以较为严格的证明了。

        CPP源码

        上面已经有了,再放一次没有意义。

        Floyd求无向图最小正环

        简介

        这个东西,GAO♂了好久才弄明白。。。QAQ

        主要用途就是对于一个给定的图,求其最小环的长度(环是指从一个节点出发,在路径不重复的情况下能回到这个点的路径,最小环是指环上的路径权值和最小的环)。

        无向图

        那么,这个图的最小环长度就是3。

        算法原理

        这个就比较难解释了哈。。。

        首先来讲,我们先要明确一下我们到底要求什么。我们要求的是最小环的长度,那么我们只要在外面定义一个变量来记录最小环的长度就行了。

        同普通的Floyd求最短路的算法是一样的,首先在最外层定义中间节点 K ,然后内层为起点和终点i j

        由于Floyd算法具有其特殊的性质(在上面的最短路用途中进行了简单证明),因此我们应该确保最短路和最小环的求取保持一致,即同时进行。

        int min_loop=INF;
        for(int k=1;k<=n;k++)
        {
            for(int i=1;i<=n;i++)
            {
                for(int j=1;j<=n;j++)
                {
                    if(!m[i][k]||!m[k][j]) continue;
                    min_loop=min(min_loop,m[i][k]+m[k][j]+d[j][i]);
                }
            }//找最小环
            for(int i=1;i<=n;i++)
            {
                for(int j=1;j<=n;j++)
                {
                    d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
                }
            }//找最短路
        }

        就像这样,不断更新,最终就成功了。。。

        正确性证明

        这个我认为它的证明跟最短路那个是一样的。。。这里不再“赘述”了。。。其实根本不会证明【滑稽】

        CPP源码

        咳,上面就是啊。。。

        例题

        这里带一道裸的题来。。。《winmt长跑》(可能无权限访问)

        《winmt长跑》

        题目背景

        winmt决定绕公园长跑了!公园中有 N 个路口,两个路口之间可能直接有道路相连,道路均为双向道路。

        winmt的跑步线路只能是一个回路,不能经过同一条路两次。也就是说,winmt可以任取一处路口出发,依次经过若干个路口,最终回到起点。由于winmt实在不想跑太长的路,于是他想请你帮他求出最优的路线。

        题目描述

        请你帮他求出最短的跑步路线。

        输入输出格式

        输入格式:

        输入中有多组数据。对于每组数据:

        第一行有两个正整数 N, M,分别表示公园中路口的个数和道路的个数,道路均为双向道路。

        以下 M 行,每行三个正整数,分别表示一条道路的两端的编号,以及这条道路的长度。 最后以 0 0 结束。

        注意:可能有重边。发现重边后,一律覆盖处理,即以最后读进来的边为准。

        输出格式:

        对于每组数据,输出一行:

        如果该回路存在,则输出一个正整数,表示该回路的总长度;否则输出”No solution.”(不要输出引号)

        输入输出样例

        输入样例#1:

        5 6
        1 4 1
        3 1 10
        1 2 16
        2 3 100
        2 5 15
        5 3 20
        4 3
        1 2 10
        1 3 20
        1 4 30
        0 0

        输出样例#1:

        61
        No solution.

        数据范围及约定

        时空限制:1000ms,512MB

        数据规模:

        对于30%的数据, n≤12;

        对于100%的数据, n≤100,道路长度≤1000,数据总组数不超过10组。

        标准的一眼题。直接套用模板就好了。

        
        #include<iostream>
        #include<cstdio>
        #include<cstring>
        using namespace std;
        const int MAXN=100;
        const int INF=0x3f3f3f3f;
        int d[MAXN+5][MAXN+5],m[MAXN+5][MAXN+5];
        int n,mm;
        int main()
        {
            while(true)
            {
                scanf("%d%d",&n,&mm);
                if(n==0&&mm==0) break;
                for(int i=1;i<=n;i++)
                {
                    memset(d[i],0x3f,sizeof(d[i]));
                    memset(m[i],0,sizeof(m[i]));
                    d[i][i]=0;
                }
                for(int i=1;i<=mm;i++)
                {
                    int u,v,w;
                    scanf("%d%d%d",&u,&v,&w);
                    d[u][v]=d[v][u]=w;
                    m[u][v]=m[v][u]=w;
                }
                int res=INF;
                for(int k=1;k<=n;k++)
                {
                    for(int i=1;i<=n;i++)
                    {
                        if(!m[i][k]) continue;
                        for(int j=i+1;j<=n;j++)
                        {
                            if(d[i][j]==INF||!m[k][j]) continue;
                            res=min(res,d[i][j]+m[i][k]+m[k][j]);
                        }
                    }
                    for(int i=1;i<=n;i++)
                    {
                        for(int j=1;j<=n;j++)
                        {
                            d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
                        }
                    }
                }
                if(res==INF) cout<<"No solution."<<endl;
                else cout<<res<<endl;
            }
            return 0;
        }
        

        后记

        好了讲完了。什么?你不懂?这不赖我。。。。。。

        理直气壮

        【理直气壮的天使小姐姐】

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值