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] 。即:

扫描二维码关注公众号,回复: 1730078 查看本文章
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;
}

后记

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

理直气壮

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

猜你喜欢

转载自blog.csdn.net/rentenglong2012/article/details/78454327