最短路

写在前面的话:写写复习下它,太久没怎么写这类题了

文章部分内容出自《算法竞赛进阶指南》

单源最短路径

这种问题就是说给一张有向图,以某一个节点(一般为1号节点),记录下其他每一个点

到达这个1号节点的最短路径的长度。

常用算法:Dijkstra,Bellman-Ford,SPFA(本质上是Bellman-Ford)

Dijkstra算法

基本思路:

它是基于贪心算法的一种方案,只能用在所有边长度非负的图。因为若z为负,全局最小值不可能被其他点

更新了。其中,第一步选出来的x已经满足--dist[x]是起点到x点的最短路径。本质上,我们在不断寻找全局

最小值在进行标记和拓展,最后就得到起点1到每个节点的最短路。

过程:

1.初始化dist[i]数组,即原点到i点的距离,dist[1]=0,其他点初始化到原点距离为+∞;

2.找出一个未被标记的且dist[x]最小的点x,再标记节点x;

3.扫描x的所有出边,若dist[y]>dist[x]+z,就更新dist[y] = dist[x]+z;

4.重复2--3,直到所有点被标记为止

当然我们可以发现,在找全局最小值的时候,代码可以继续优化,即找这个最小值可以用二叉堆去找,

复杂度从O($n^{2}$) 进化成 O($m log n$)。

#include<iostream>
#include<queue> 
#include<cstring>
#include<cstdio>
using namespace std;
const long MAXN=1000010;
long n,m;
long head[MAXN],Next[MAXN],ver[MAXN],edge[MAXN],tot=0;
priority_queue< pair<long,long> > q;
inline void add(long x,long y,long z){
    ver[++tot]=y,edge[tot]=z,Next[tot]=head[x],head[x]=tot;
}
bool v[MAXN];
long dist[MAXN];
inline void dijkstra(){
    for(long i=1;i<=n;i++) dist[i]=999999;
    memset(v,0,sizeof v);
    dist[1]=0;
    q.push(make_pair(0,1));
    while(q.size()){
        long x=q.top().second;
        q.pop();
        if(v[x]) continue;
        v[x]=1;
        for(long i=head[x];i;i=Next[i])
        {
            long y=ver[i],z=edge[i];
            if(dist[y]>dist[x]+z){
            dist[y]=dist[x]+z;
            q.push(make_pair(-dist[y],y));
            }
        }
    }
    
}
int main(){
    scanf("%ld%ld",&n,&m);
    long x,y,z;
    for(long i=1;i<=m;i++){
    scanf("%ld%ld%ld",&x,&y,&z);
    add(x,y,z);
    }
    dijkstra();
    for(long i=1;i<=n;i++)
    printf("%ld\n",dist[i]); 
}

Bellman-Ford算法

基本思路

它是基于迭代的思想去考虑,即使所有边(x,y,z)满足三角形不等式:d[y]<=d[x]+z

如下图:

 

过程:

1.扫描所有边,若dist[y]>dist[x]+z,就使dist[y]=dist[x]+z;

2.重复上述步骤直到没有更新发生为止。

复杂度高达O(nm),但它优于dijkstra的一点,是它的边权可以为负值。

SPFA算法

基本思路:

spfa在国际上通称”队列优化的Bellman-Ford算法“,它用队列保存了待扩展的节点,每一次的入队

相当于完成一次更新,使得节点慢慢收敛,即松弛,稀疏图效率高。

因为对于一个从x到y的边,节点x还没有松弛过,那y就没必要松弛,所以我们用队列记录松弛过的点,

避免了bellman-ford中的冗余松弛操作。

过程:

1.建立一个队列,初始入队节点1;

2.取出队头,并扫描所有出边,若dist[y]>dist[x]+z,则更新dist[y]=dist[x]+z,并判断y是否在队列中,若

不在,则将y入队;

3.重复上述步骤直至队列为空。

用简略语言概述就是,我们有一个松弛点x,尝试x能到达的点y,若y可以被松弛,就更新dist[y],在

这个条件下,若y还没入队,那就把这个松驰过的点入队,直到队列为空。

#include<iostream>
#include<queue> 
#include<cstring>
#include<cstdio>
using namespace std;
const long MAXN=1000010;
long n,m;
long head[MAXN],Next[MAXN],ver[MAXN],edge[MAXN],tot=0;
queue <long> q;
inline void add(long x,long y,long z){
    ver[++tot]=y,edge[tot]=z,Next[tot]=head[x],head[x]=tot;
}
bool v[MAXN];
long dist[MAXN];
inline void spfa(){
    for(long i=1;i<=n;i++) dist[i]=999999;
    memset(v,0,sizeof v);
    dist[1]=0;
    q.push(1);
    v[1]=1;
    while(q.size()){
        long x=q.front();
        q.pop();
        v[x]=0;
        for(long i=head[x];i;i=Next[i])
        {
            long y=ver[i],z=edge[i];
            if(dist[y]>dist[x]+z){
            dist[y]=dist[x]+z;
            if(v[y]==0) q.push(y),v[y]=1;
            }
        }
    }
    
}
int main(){
    scanf("%ld%ld",&n,&m);
    long x,y,z;
    for(long i=1;i<=m;i++){
    scanf("%ld%ld%ld",&x,&y,&z);
    add(x,y,z);
    }
    spfa();
    for(long i=1;i<=n;i++)
    printf("%ld\n",dist[i]); 
}

特别地,当我们要去判定负环的时候,我们可以用一个数组c[i]表示1到i的最短路径上点的个数,每一次松弛x-y时,

我们就用c[y]=c[x]+1,当c[y]>n时,即存在负环。

注:

其实对比dijkstra和spfa,我们可以发现,dijkstra针对每一个最小距离点进行扩展,

spfa则是通过迭代思想将所有边进行扩展,自然spfa的复杂度会高于dijkstra,但是spfa同时也具有

可以更加容易加入各种其他算法和对负权的边,负环的处理。

温馨提示,面对一个不存在负权边的图最好用dijkstra,因为spfa易被恶意卡掉。

任意两点间的最短路径

Floyd算法

基本思路:

它是一种基于动态规划的算法,我们可以用D[k,i,j]来表示i经过编号不超过k的节点后到达j的最短路,所以

我们可以将其划分为两个子问题,一是congi经过编号不超过k-1的节点到j,或者从i经过k节点到达j。

则 D[k,i,j]=min(D[k-1,i,j],D[k-1,i,k]+D[k-1,k,j])

所以k是阶段,应该放在最外层

我们也可以用D保存邻接矩阵,省略k这一维,即:

D[i,j]=min(D[i,j],D[i,k]+D[k,j])

也可以理解为从i到j,看看目前的情况好,还是再经过一个中间点k更好。

过程:

这就不再赘述了,太过简单......

 

#include<iostream>
#include<cmath>
using namespace std;
const long MAXN=10010;
long dist[MAXN][MAXN];
long n,m;
int main(){
    cin>>n>>m;
    for(long i=1;i<=n;i++)
    for(long j=1;j<=n;j++) 
    dist[i][j]=999999;
    for(long i=1;i<=n;i++) 
    d[i][i]=0;
    long x,y,z;
    for(long i=1;i<=m;i++){
        cin>>x>>y>>z;
        if(dist[x][y]>z) dist[x][y]=z;
    }
    for(long k=1;k<=n;k++)
    for(long i=1;i<=n;i++)
    for(long j=1;j<=n;j++)
    dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
    for(long i=1;i<=n;i++){
    for(long j=1;j<=n;j++)
    cout<<dist[i][j]<<" ";
    cout<<endl;
    }
}

范围,数值最大值可以自行更改

传递闭包

在交际网络中存在若干元素和若干对二元关系,关系具有传递性,请推导出尽量多的元素间的关系----传递闭包

易得,我们可以用floyd解决此类问题

#include<iostream>
#include<cmath>
using namespace std;
const long MAXN=10010;
bool dist[MAXN][MAXN];
long n,m;
int main(){
    cin>>n>>m;
    for(long i=1;i<=n;i++) 
    dist[i][i]=1;
    long x,y,z;
    for(long i=1;i<=m;i++){
        cin>>x>>y;
        dist[x][y]=dist[y][x]=1;
    }
    for(long k=1;k<=n;k++)
    for(long i=1;i<=n;i++)
    for(long j=1;j<=n;j++)
    dist[i][j]|=dist[i][k]&dist[k][j];
    
}

 

posted @ 2020-07-22 21:22  愚者123  阅读(198)  评论(0编辑  收藏  举报