最短路相关模型及应用整理

这里我们开始分析最短路问题,我们先来看最基础的最短路算法:

下面我们分析下每种算法的思路,并不展开论述例题,例题的分析求解放在最后的链接里。

朴素版Dijkstra

实现思路如下:
1.将起点距离初始化成0,其他点的距离初始化成正无穷
2.用一个集合s表示当前已经确定最短路的点(实际实现用的是st[]数组标记),我们进行n次循环,每次循环找出一个不在集合中的,同时距离起点最近的点,这个点的距离就可以被确定为这一点到起点的最小距离。
3.然后用这个点去更新其他各点到起点的距离
我们一次循环确定一个点,那么n次循环就可以将所有的都确定下来。

核心代码:

int st[],d[];
int g[][];//邻接矩阵,用来表示点与点之间的关系,不连通就是正无穷,连通的话就是边权
int n;
void dijkstra(int s)//s表示起点
{
    memset(d,0x3f,sizeof d);
    memset(st,0,sizeof st);
    d[s]=0;
    for(int c=1;c<=n;c++)
    {
        int t=-1;
        for(int i=1;i<=n;i++)
        {
            if(!st[i]&&(t==-1||d[i]<d[t]))t=i;
        }
        st[t]=1;
        for(int i=1;i<=n;i++)
                d[i]=min(d[i],d[t]+g[t][i]);//已经确定的肯定最短,不连通的是正无穷,肯定不能被用,所以没有特判的必要
    }
}

堆优化版的Dijkstra

我们观察上面的那个过程,时间复杂度是O(n^2)的,如果n到1e5的数量级了,那么就会超时,显然不够好,我们来想一下,上面到n^2主要是因为有嵌套循环。那么我们看看是否可以对嵌套循环进行优化,首先是找还没有被确定的所有点中最近的点,我们可以用一个堆来实现,用一个小根堆,每次取堆顶的复杂度就是O(1)。同时我们也可以避免循环n次来查找,直接用一个优先队列,一直循环到队列为空即可。然后更新其他点的话,我们可以用邻接表存点与点的关系,然后只更新与这个点相连的点即可,那么就避免了遍历所有点。所以整体的思路如下:
1.首先对于距离已经更新的点,我们将它放入优先队列中,每次取队头,
2.每次取队头,然后将队头加入集合s(实际就是st),表示当前点到起点的最短距离已经确定
3.更新与这个点直接相连的点。

int h[],e[],ne[],w[];
int st[];
typedef pair<int,int> pii;
void dijkstra(int s)
{
    priority_queue<pii,vector<pii>,greater<pii>>q;//小根堆
    memset(d,0x3f,sizeof d);
    d[s]=0;
    q.push({d[s],s});
    while(q.size())
    {
        auto t=q.top();
        q.pop();
        int dist=t.first,v=t.second;
        if(st[v]) continue;//队列中不能直接修改,所以有冗余
        st[v]=1;
        for(int i=h[v];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(d[j]>d[v]+w[i])
            {
                d[j]=d[v]+w[i];
                q.push({d[j],j});
            }
        }
    }
}

ps:鉴于时间复杂度,朴素版一般用来求解稠密图,即边比较多的图,而优化版一般用来求解稀疏图,即边比较少的图。

Bellman-ford算法

实现思路:
1.将所有的点都初始化成正无穷,起点初始化成0;
2.循环n次,每次遍历所有的边(a,b,w),并更新d[b]=min(d[b],r[a]+w)

已被证明第k次循环更新出来的距离表示每个点到起点经过不超过k条边的最短距离。这里用来找最短路或者找负权回路的话时间复杂度都较高,所以这个算法一般用来找从起点到任意一点经过不超过k条边的最短路。(有边数限制的最短路问题)

这里还有一点需要注意,我们在更新距离的时候并非用的d[a],而是用的r[a],r[a]是上一层的数据,这里是为了防止本层更新过的点用本层数据去更新别的点,这样边数就不能保证了。

struct egde{
    int a,b,w;
}e[];
int d[],r[];
void bellman_ford(int s)
{
    memset(d,0x3f,sizeof d);
    d[s]=0;
    for(int i=0;i<k;i++)
    {
        memcpy(r,d,sizeof d);
        for(int j=0;j<m;j++)
        {
            int a=e[j].a,b=e[j].b,c=e[j].c;
            d[b]=min(d[b],r[a]+c);
        }
    }
}

SPFA

spfa是对bellman_ford的优化,我们很容易发现在bellman-ford算法中的遍历每一条边是有冗余的,我们可以只遍历那些被更新过的点连出去的边,因为其他的边遍历了也未必会产生效更新(用未必和有效更新是因为可能会有负权边)。这里可以用一个队列来实现,将被更新的同时不在队列中的点放入队列中。这里的队列用手写队列或者queue都可。

int h[],e[],ne[],w[];
void spfa(int s)
{
    memset(d,0x3f,sizeof d);
    queue<int>q;
    d[s]=0;
    q.push(s);
    while(q.size())
    {
        int t=q.front();
        q.pop();
        st[t]=0;
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(d[j]>d[t]+w[i])
            {
                d[j]=d[t]+w[i];
                if(!st[j]) 
                {
                    q.push(j);
                    st[j]=1;
                }
            }
        }
    }
    
}

Floyd

floyd算法可以用来求任意两点之间的最短路,这里用到dp的思想,准确来说是区间dp,很显然任意两个点之间视为一个区间,它们之间的最短路可能会用任意点来进行中转,我们从这一点入手写一个三重循环即可。

int g[][];
void Floyd()
{
    for(int k=1;k<=n;k++)
    {
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
            }
        }
    }
}

应用

单源最短路建图方式例题整理-CSDN博客

单源最短路的综合应用例题整理-CSDN博客 

单源最短路的扩展应用-CSDN博客 

Floyd算法例题整理-CSDN博客 

  • 25
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值