zoukankan      html  css  js  c++  java
  • 玄学最短路算法——Ex Floyd

    Floyd再思考

    ​ ——by ThinkofBlank

    一.序言

    ​ Floyd,是一个十分常用的图论算法,其作用是在O(n^3)的时间内计算出全源最短路。其实现原理是利用的dp,然而,刚开始接触Floyd的时候,并没有去尝试理解,思考此算法,仅仅记了下打法就跑了,最近无聊时思考了下,得出了些有用的东西,特此来分享

    ​ 注意:以下说的图都是无负环的,不然,求甚么最短路。。。

    ​ 当然,给出的图是联通且点的个数至少为3

    二.引入

    ​1、路径合成可行性

    给定一个图,假设我们已知i->j的最短路径为a1,i->k的最短路径为a2,(a1<a2),那么j->k的最短路径是多少?

    答案是a2-a1?不!并不是,比如下图:

    很明显的,j->k的最短路径应该是a1+a2才对,当然,也有满足最短路径为a1-a2的情况,如下图:

    所以,如果我们单纯地知道i->j和i->k的最短路径,我们是无法求出正确的j->k的最短路径的

    那么,我们来换一换,假设我们已知i->k的最短路径为a1,k->j的最短路径为a2,那么i->j的最短路径是不是就可求呢?

    初步画个图:

    由此可以判断出i->j的最短路径是a1+a2,于是就又错啦!

    以下给出两个hack的情况:

    当然,对于第一个hack的情况,要j和k之间的是双相边

    于是,我们真的无法求出来了吗?不!

    我们注意到,对于两种hack的情况,其实它都可以看出i->k,k->j的行走方式,只是某些时候,k=i或j罢了!

    所以,就有:对于一条路径i->j,它可以由i->k,k->j合成,那么我们只需要合成就好了。这就是路径的合成可行性

    感性的理解:由i到j,我们可以先从i到k,再从k到j,如果直接由i到j中途不停留的话,那么相当于此时k=i或者j

    那么,这里就有一个方法了!

    我们枚举所有的k!这样,我们就可以枚举出所有的路径出来!然后把这些权值取最小,即一定为最小路径!

    首先,我们设dis(i,j)表示i->j的最短路径(初始无限大),那么如果有一条边权为w从i->j的边,我们就令dis(i,j)=min(dis(i,j),w)

    这样我们就求出了对于所有i->k,k->j中k=i或j的时候的路径了

    那么之后怎么求呢?

    简单啊!

    枚举k嘛,我就老老实实枚举k和i,j就好了嘛

    for:i=1->n

    ​ for:j=1->n

    ​ for:k=1->n

    ​ dis(i,j)=min(dis(i,j),dis(i,k)+dis(k,j))

    看,这不就好了嘛!

    然而,这是错的。。。

    为啥?

    因为我们回过去看枚举所有k得到最短路径的条件:dis(i,k)与dis(k,j)一定也是最短路径!

    但是,我们目前,我们对于部分dis(i,k)和dis(k,j)并没有枚举,所以,我们的答案不一定正确!

    那么该如何改进呢?

    传统的Floyd是使用松弛操作进行的:

    for:k=1->n

    ​ for:i=1->n

    ​ for:j=1->n

    ​ dis(i,j)=min(dis(i,j),dis(i,k)+dis(k,j))

    咋看一下没啥不同,但事实上,它是在进行dp,具体的我就不说多了,现在来讨论下Floyd的Ex算法:

    先来引入一个定理:

    2、一定存在两个点最短路径为它们两个点的直接连边

    先者有言:没有证明的理论,都是猜想,不能直接用

    那么,我们就来证明一波

    我使用的是反证法:

    假设所有点两两之间的最短路都间隔了至少经过了一个点

    我们设i->j的最短路为i->k->j

    那么,不难发现,如果i->k的最短路不是i,k的直接连边,而是i->t->k的话,那么明显有i->j的最短路中

    i->t->k->j更优

    那么i->j的最短路不止经过一点呢,我们直接假设为i->t->k->j,我们可以同理去分析i->t

    所以,这与假设不相符

    所以,命题得证。

    而且,同理去分析k->j,还会再多得一对点,所以对于任意一对最短路为直接连边的两点A,B

    我们一定可以找到另外的一个点C,使得那个点与两点中的其中一个点(假设为B)的直接连边是最小路径

    简单分析下便可发现,这样的点对至少有n-1个

    那么,如果我们找到所有这样的点对,我们就可以再推出至少n-2条最短路径,然后再用这n-2条路径继续推...

    循环n-1次便可以推完所有的最短路径了!

    三、算法

    那,我们怎么找到这n-1条边呢?对于一个图,我们可以采用玄学的方法——枚举法!

    这样,我们就有了一个算法的雏形:

    一开始的赋值就不说了。。。

    首先,我们将所有边加入对应的表中(使用数组即可~),比如u->v,我们就令s(u,++siz(u))=v,表示,我们有一条从

    u->v的确定了权值的边(不一定最小)

    然后,我们进行一下操作:

    1.枚举i

    2.访问与i相连的边,找到一个k

    3.访问与k相连的边,找到一个j

    4.如果dis(i,k)+dis(k,j)<dis(i,j),我们令dis(i,j)=dis(i,k)+dis(k,j),并把j加入i的表中

    5.回到步骤1,重复n-2次即可(第一次是赋值那里)

    两个优化:

    1.对于每个点来说,我们维护一个bool数组ys(i,j)表示j是否在i的表里面,如果是,我们就无需把j加入表中,否则,我们就将j加入表中,并使ys(i,j)=1

    2.对于不在起作用的表,我们将它砍掉,无需再访问(详细见代码的kil数组)

    于是我们有了代码:

    //#pragma GCC optimize()//手动Ox优化
    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    const int M=1001;
    int dis[M][M];
    int s[M][M],siz[M];
    bool kil[M][M];
    bool ys[M][M];
    inline void Ex_Floyd(int n){
    	int tim=n-2;
    	while(tim--){
    		bool flag=0;
    		for(int i=1;i<=n;++i){
    			for(int j=1;j<=siz[i];++j){
    				int v=s[i][j];
    				if(kil[i][j]){
    					continue;
    				}
    				kil[i][j]=1;
    				for(int k=1;k<=siz[v];++k){
    					int e=s[v][k];
    					if(dis[i][e]>dis[i][v]+dis[v][e]){
    						dis[i][e]=dis[i][v]+dis[v][e];
    						if(!ys[i][e]){
    							ys[i][e]=1;
    							s[i][++siz[i]]=e;
    						}
    						flag=1;
    						kil[i][j]=0;
    					}
    				}
    			}
    		}
    		if(!flag){
    			break;
    		}
    	}
    }
    signed main(){
    	int n,m;
    	scanf("%lld%lld",&n,&m);
    	for(int i=1;i<=n;++i){
    		for(int j=1;j<=n;++j){
    			dis[i][j]=1e16;
    		}
    		dis[i][i]=0;
    	}
    	for(int i=1;i<=m;++i){
    		int u,v,w;
    		scanf("%lld%lld%lld",&u,&v,&w);
    		if(dis[u][v]>w){
    			if(!ys[u][v]){
    				ys[u][v]=1;
    				s[u][++siz[u]]=v;
    			}
    			dis[u][v]=w;
    		}
    	}
    	Ex_Floyd(n);
    	return 0;
    }
    

    我们把它称作Ex_Floyd算法,为什么呢?我们通过一道题来说明:

    http://ybt.ssoier.cn:8088/problem_show.php?pid=1421

    我们惊奇的发现,传统的Floyd算法竟然无法通过此题,而此算法可以

    经过尝试发现,Floyd错的点中,存在负权边

    事实证明,传统的Floyd似乎不能跑负权边啊。。。

    而此算法可以,故命名为Ex_Floyd(经过一番尝试后,发现,此算法应与Floyd的复杂度接近,不过常数可能会大点)

  • 相关阅读:
    python3的pygame的五子棋布局设置和代码详细分析
    RAID原理分析
    Python攻城狮教你用Pythin开机和关机,关机只需一条执行命令
    Python 之 MySql 每日一练 329——查询名字中含有风字的学生信息
    Python 之 MySql 每日一练 232——查询每门课程的平均成绩
    网络虚拟化基础协议·Geneve
    openstack octavia的实现与分析(一)openstack负载均衡的现状与发展以及lvs,Nginx,Haproxy三种负载均衡机制的基本架构和对比
    Linux下多网卡绑定bond及模式介绍
    Python调试器-pdb的使用
    ubuntu server安装的一些坑
  • 原文地址:https://www.cnblogs.com/ThinkofBlank/p/10427145.html
Copyright © 2011-2022 走看看