玄学最短路算法——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的复杂度接近,不过常数可能会大点)

原文地址:https://www.cnblogs.com/ThinkofBlank/p/10427145.html

时间: 2024-10-15 15:46:16

玄学最短路算法——Ex Floyd的相关文章

【啊哈!算法】算法6:只有五行的Floyd最短路算法

暑假,小哼准备去一些城市旅游.有些城市之间有公路,有些城市之间则没有,如下图.为了节省经费以及方便计划旅程,小哼希望在出发之前知道任意两个城市之前的最短路程. 上图中有4个城市8条公路,公路上的数字表示这条公路的长短.请注意这些公路是单向的.我们现在需要求任意两个城市之间的最短路程,也就是求任意两个点之间的最短路径.这个问题这也被称为“多源最短路径”问题. 现在需要一个数据结构来存储图的信息,我们仍然可以用一个4*4的矩阵(二维数组e)来存储.比如1号城市到2号城市的路程为2,则设e[1][2]

最短路算法 :Bellman-ford算法 &amp; Dijkstra算法 &amp; floyd算法 &amp; SPFA算法 详解

 本人QQ :2319411771   邮箱 : [email protected] 若您发现本文有什么错误,请联系我,我会及时改正的,谢谢您的合作! 本文为原创文章,转载请注明出处 本文链接   :http://www.cnblogs.com/Yan-C/p/3916281.html . 很早就想写一下最短路的总结了,但是一直懒,就没有写,这几天又在看最短路,岁没什么长进,但还是加深了点理解. 于是就想写一个大点的总结,要写一个全的. 在本文中因为邻接表在比赛中不如前向星好写,而且前向星效率并

最短路算法(dijkstra,bellman_ford,floyd)

最短路算法 dijkstra(初级的最短路算法,适合稠密图,可用邻接表优化) bool relax(int u,int v) { double tmp=max(dist[u],edge[u][v]); if(tmp<dist[v]){ dist[v]=tmp; } } void dijkstra() { memset(vis,0,sizeof(vis)); for(int i=0;i<n;i++){ int x; double mindist=INF; for(int j=0;j<n;j

图论之最短路01——最短路矩阵(FLOYD)算法

%======================================================== %最短路矩阵算法,FLOYD算法 %针对性:方案预算,能求出所有点之间的最短路(最小费用等) %======================================================== function D=zuiduanjulijuzhen(quanzhijuzhen) n=length(quanzhijuzhen); D=quanzhijuzhen; m

【啊哈!算法】算法7:Dijkstra最短路算法

上周我们介绍了神奇的只有五行的Floyd最短路算法,它可以方便的求得任意两点的最短路径,这称为“多源最短路”.本周来来介绍指定一个点(源点)到其余各个顶点的最短路径,也叫做“单源最短路径”.例如求下图中的1号顶点到2.3.4.5.6号顶点的最短路径. <ignore_js_op> 与Floyd-Warshall算法一样这里仍然使用二维数组e来存储顶点之间边的关系,初始值如下. <ignore_js_op> 我们还需要用一个一维数组dis来存储1号顶点到其余各个顶点的初始路程,如下.

最短路算法汇总

校赛完了,这次校赛,做的很差,一个算法题没有,2个水题,1个贪心,概率DP,DP,数论题.DP还没开始研究,数论根本不会,数学太差了,省赛时卡数论,校赛依然卡数论,我擦,还是得继续学习啊! 一把锈迹斑斑的剑,只有不断的磨砺,才能展露锋芒! 以下为最短路总结: 最短路问题可分为: 一.单源最短路径算法,解决方案:Bellman-Ford算法,Dijkstra算法,SPFA 二.每对顶点间的最短路径算法:Floyd: (1).Dijkstra算法: (经典的算法,可以说是最短路问题的首选事例算法,但

最短路算法及其延伸

个人算法训练题集:http://acm.hust.edu.cn/vjudge/contest/toListContest.action#contestType=0&contestRunningStatus=0&contestOpenness=0&title=风斩冰华&manager= 密码xwd,欢迎大家一起来学习. 首先复习一下最短路问题,即求某两点之间边权最小的一条路径.这样就延伸出了两个子问题: 求任意两点的距离,还是求图上固定一个起点到某点的距离? 验题:http:

最短路算法--模板

迪杰斯特拉算法(Dijkstra): Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径. 主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止. 1 #include <iostream> 2 using namespace std; 3 const int maxnum = 100; 4 const int maxint = 999999; 5 6 // 各数组都从下标1开始 7 int dist[maxnum]; // 表示当前点到源

彻底搞懂最短路算法

转载自:戳 彻底弄懂最短路径问题 只想说:温故而知新,可以为师矣.我大二的<数据结构>是由申老师讲的,那时候不怎么明白,估计太理论化了(ps:或许是因为我睡觉了):今天把老王的2011年课件又看了一遍,给大二的孩子们又讲了一遍,随手谷歌了N多资料,算是彻底搞懂了最短路径问题.请读者尽情享用…… 我坚信:没有不好的学生,只有垃圾的教育.不过没有人理所当然的对你好,所以要学会感恩. 一.问题引入 问题:从某顶点出发,沿图的边到达另一顶点所经过的路径中,各边上权值之和最小的一条路径——最短路径.解决