单源最短路径算法总结

Posted bcoier

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单源最短路径算法总结相关的知识,希望对你有一定的参考价值。

这里给大家介绍三种最短路常用算法:

floyd(O(n^3))、dijkstra(O(nlogn))、SPFA(O(KE))(k是进队列次数,在没有负环的情况下2)

其实还有一个Bellman-Ford(O(nm))算法,但由于不常用而且SPFA是这个算法的改进版本,在这里就不列举了

floyd:效率较低,只有70分

具体思路:将所有节点的距离都存在一个数组里,由于要枚举所有的两两组合以及每一个组合的“中转点”,再进行松弛操作

在求单源最短路径的时候就会浪费许多空间,但在求多源最短路时,复杂度仍是O(n^3)使用很广

#include<bits/stdc++.h>
using namespace std;
#define inf 1234567890
#define maxn 10005
inline int read()
{
    int x=0,k=1; char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')k=-1;c=getchar();}
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*k;
}//快读
int a[maxn][maxn],n,m,s;
inline void floyd()
{
    for(int k=1;k<=n;k++)
    //这里要先枚举k(可以理解为中转点)
    {
        for(int i=1;i<=n;i++)
        {
            if(i==k||a[i][k]==inf)
            {
                continue;
            }
            for(int j=1;j<=n;j++)
            {
                a[i][j]=min(a[i][j],a[i][k]+a[k][j]);
                //松弛操作,即更新每两个点之间的距离
                //松弛操作有三角形的三边关系推出
                //即两边之和大于第三边
            }
        }
    }
}
int main()
{
    n=read(),m=read(),s=read();
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            a[i][j]=inf;
        }
    }
    //初始化,相当于memset(a,inf,sizeof(a))
    for(int i=1,u,v,w;i<=m;i++)
    {
        u=read(),v=read(),w=read();
        a[u][v]=min(a[u][v],w);
        //取min可以对付重边
    }
    floyd();
    a[s][s]=0;
    for(int i=1;i<=n;i++)
    {
        printf("%d ",a[s][i]);
    }
    return 0;
}

dijkstra:对于无负边的情况下可以达到O(nlogn)且很难被卡

具体思路:Dijkstra是基于一种贪心的策略,首先用数组dis记录起点到每个结点的最短路径,再用一个数组保存已经找到最短路径的点

然后,从dis数组选择最小值,则该值就是源点s到该值对应的顶点的最短路径,并且把该点记为已经找到最短路

此时完成一个顶点,再看这个点能否到达其它点(记为v),将dis[v]的值进行更新

不断重复上述动作,将所有的点都更新到最短路径

这种算法实际上是O(n^2)的时间复杂度,但我们发现在dis数组中选择最小值时,我们可以用一些数据结构来进行优化。线段树?平衡树?

其实我们可以用STL里的堆来进行优化,堆相对于线段树以及平衡树有着常数小,码量小等优点,并且堆的一个妙妙的性质就是可以在nlogn的时限内满足堆顶是堆内元素的最大(小)值,之不正是我们要的嘛?

以下是用堆优化dijkstra代码:
#include<bits/stdc++.h>
using namespace std;
#define maxn 10005
#define maxm 500005
#define INF  1234567890
inline int read()
{
    int x=0,k=1; char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')k=-1;c=getchar();}
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*k;
}
struct Edge
{
    int u,v,w,next;
}e[maxm];
int head[maxn],cnt,n,m,s,vis[maxn],dis[maxn];
struct node
{
    int w,now;
    inline bool operator <(const node &x)const
    //重载运算符把最小的元素放在堆顶(大根堆)
    {
        return w>x.w;//这里注意符号要为'>'
    }
};
priority_queue<node>q;
//优先队列,其实这里一般使用一个pair,但为了方便理解所以用的结构体
inline void add(int u,int v,int w)
{
    e[++cnt].u=u;
    //这句话对于此题不需要,但在缩点之类的问题还是有用的
    e[cnt].v=v;
    e[cnt].w=w;
    e[cnt].next=head[u];
    //存储该点的下一条边
    head[u]=cnt;
    //更新目前该点的最后一条边(就是这一条边)
}
//链式前向星加边
void dijkstra()
{
    for(int i=1;i<=n;i++)
    {
        dis[i]=INF;
    }
    dis[s]=0;
    //赋初值
    q.push((node){0,s});
    while(!q.empty())
    //堆为空即为所有点都更新
    {
        node x=q.top();
        q.pop();
        int u=x.now;
        //记录堆顶(堆内最小的边)并将其弹出
        if(vis[u]) continue; 
        //没有遍历过才需要遍历
        vis[u]=1;
        for(int i=head[u];i;i=e[i].next)
        //搜索堆顶所有连边
        {
            int v=e[i].v;
            if(dis[v]>dis[u]+e[i].w)
            {
                dis[v]=dis[u]+e[i].w;
                //松弛操作
                q.push((node){dis[v],v});
                //把新遍历到的点加入堆中
            }
        }
    }
}
int main()
{
    n=read(),m=read(),s=read();
    for(int i=1,x,y,z;i<=m;i++)
    {
        x=read(),y=read(),z=read();
        add(x,y,z);
    }
    dijkstra();
    for(int i=1;i<=n;i++)
    {
        printf("%d ",dis[i]);
    }
    return 0;
}

SPFA:考场慎用,在毒瘤数据面前可能退化到O(nm)

具体思路:这里用的是STL队列,首先用数组dis记录起点到每个结点的最短路径,用邻接表来存储图,用vis数组记录当前节点是否在队列中

具体操作为:用队列来保存待优化的结点(类似于BFS),优化时每次取出队首结点,并且用队手节点来对最短路径进行更新并进行松弛操作

如果要对所连点的最短路径需要更新,且改点不在当前的队列中,就将改点加入队列

然后不断进行松弛操作,直至队列空为止。


#include<bits/stdc++.h>
using namespace std;
inline int read()
{
    int x=0,k=1; char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')k=-1;c=getchar();}
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*k;
}
#define maxn 10005
#define maxm 500005
#define inf 1234567890
int n,m,s,tot,dis[maxn],head[maxn];
bool vis[maxn];
struct Edge
{
      int next,to,w;
}h[maxm];
void add(int u,int v,int w)
{
    h[++tot].next=head[u];
    h[tot].to=v;
    h[tot].w=w;
    head[u]=tot;
}
//上面和dijkstra算法基本上一样
queue<int> q;
//队列优化
inline void spfa()
{
    for(int i=1; i<=n; i++)
    {
        dis[i]=inf;
        //赋初值
    }
    int u,v;
    q.push(s);
    dis[s]=0;
    //将起点的值负为0
    vis[s]=1;//这句话可加可不加,因为循环的时候vis[s]又会被赋为0
    while(!q.empty())
    //当队列里没有元素的时候,那就已经更新了所有的单源最短路径
    {
        u=q.front();
        //将队手节点记录并弹出队首节点
        q.pop();
        vis[u]=0;
        for(int i=head[u];i;i=h[i].next)
        //寻找与u相连的边
        {
            v=h[i].to;
            if(dis[v]>dis[u]+h[i].w)
            {
                dis[v]=dis[u]+h[i].w;
                //松弛操作,和floyd比较相似
                if(!vis[v])
                {
                //已经在队列里的点就不用再进入了
                      vis[v]=1;
                      q.push(v);
                }
            }
        }
    }
}
int main(){
    n=read(),m=read(),s=read();
    for(int i=1,u,v,w;i<=m;i++)
    {
        u=read(),v=read(),w=read();
        add(u,v,w);
    }
    spfa();
    for(int i=1; i<=n; i++)
    {
        printf("%d ",dis[i]);
    }
    return 0;
}

以上是关于单源最短路径算法总结的主要内容,如果未能解决你的问题,请参考以下文章

单源最短路径Dijkstra算法的思想详细步骤代码

贪心算法—单源最短路径

图文解析 Dijkstra单源最短路径算法

算法之单源最短路径

单源最短路径

数据结构—— 图:最短路径问题