最短路(一)dijkstra算法浅析

最短路浅析——dijkstra

dijkstra是一个基于贪心思想的用来求单源最短路径的算法

给定一个带权有向图G=(V,E),其中每条边的权是一个实数。另外,还给定V中的一个顶点,称为源。现在要计算从源到其他所有各顶点的最短路径长度。这里的长度就是指路上各边权之和。这个问题通常称为单源最短路径(以下简称最短路)问题。(摘自百度百科)

1.dijkstra简述

Dijkstra提出按各顶点与源点v间的路径长度的递增次序,生成到各顶点的最短路径的算法。既先求出长度最短的一条最短路径,再参照它求出长度次短的一条最短路径,依次类推,直到从源点v 到其它各顶点的最短路径全部求出为止。
具体步骤
1、选一顶点v为源点,并视从源点v出发的所有边为到各顶点的最短路径(确定数据结构:因为求的是最短路径,所以①就要用一个记录从源点v到其它各顶点的路径长度数组dist[],开始时,dist是源点v到顶点i的直接边长度,即dist中记录的是邻接阵的第v行。②设一个用来记录从源点到其它顶点的路径数组path[],path中存放路径上第i个顶点的前驱顶点)。
2、在上述的最短路径dist[]中选一条最短的,并将其终点(即

2.模拟算法过程

如果上面的内容似懂非懂,那么就来看一下这个算法的实现过程吧。
首先来张图
这里写图片描述

蓝色为每条边的边权值,箭头代表边的方向
一号点为起点,求1到7的最短路
这里写图片描述
红色代表每个点的dis值,也就是每个点到起点(1)的最短路长度,初始我们将每个点到起点的最短路长度设为无限大
而1到1自己的距离为0.
点变红代表我们访问了这个点
接下来我们从1开始向外延伸
这里写图片描述

1号点指向了两个点,分别是2和3
到2号点的距离是dis[1]+val[1][2](1到2的边权),距离为3
我们将3和dis[2]比较,发现3更小,所以更新二号点的dis值为3
三号点也是同理
然后我们从图中找dis值最小且没有访问过的点,发现是2号点,于是进入2号点
这里写图片描述

将2号点标记为访问过
然后按上面的方法继续更新它指向的4号点和5号点
继续寻找图中dis值最小且没有访问过的点,发现是3号点,于是进入3号点
这里写图片描述
将3号点标记为访问过,更新3号点指向的4号点和6号点
这时我们发现,dis[3]+val [3,4]=6
小于原先的dis[4]=8
所以我们将dis[4]原先的8更改为6
继续寻找图中dis值最小且未访问的点
找到了4号点
这里写图片描述

同理,我们发现从4到6这条路径上
dis[4]+val[4,6]=8
小于dis[6]=9 所以我们将dis[6]更新为更小的8
然后进入6号点
这里写图片描述

dis[6]+val[6,7]=10 小于dis[7]=INF 所以将dis[7]更新为10
进入5号点
这里写图片描述

发现5到6这条路径,dis[5]+val[5,6]=10 大于dis[6]=8 所以更新不成功,dis[6]仍然为8
同理dis[7]仍然为10
进入7号点
发现7号点出度为0,且图中所有点都被访问过,循环结束
这里写图片描述
此时输出dis[7],即为1(起点)到7(终点)的最短路
同理,dis[i]可以表示1到i号点的最短路长度,即上图红色数字

算法模拟结束

3.疑问与细节

1.vis数组存在的必要性
为什么要有vis数组来记录每个点是否被访问过?
原因很简单,如果不记录每个点是否被访问过,当遇到两点之间有双向边时,可能会无限循环下去。(从一点到达另一点,再从另一点回来,反复如此)

2.图的连通性
有些题中并没有保证图的连通性,我们在遍历节点的时候可能会遍历到dis值未被更新过的点,即为无限大。这样的点是无法到达起点的,需要按照具体题目要求进行处理。

3.为什么要每次找到dis值最小的点?
举个很简单的栗子,看下图
这里写图片描述
1节点更新的2和3的dis值分别为4和1,这时本应该进入dis值最小的3,如果我们进了2号节点,那么2号点会继续更新4号点,此时4号点的dis值变为5
然后我们再进入3号点,更新dis[2]的值,然后发现2号点已经访问过了,退出循环
这时我们得到的dis[4]是错误的答案。
所以先进入dis值最小的点是很有必要的,这就是dijkstra算法中贪心思想的体现。

4.优点与局限

dijkstra可以很快求出单源最短路径,并且拥有较稳定的时间复杂度,能很好地处理大多数最短路问题。
然而,dijkstra的局限性体现在了负边和极端链状图上
如果一个图中存在了负边,过一次负边路径长度会减小,dijkstra同样会出问题
比如
这里写图片描述
自己模拟一下就会发现,4号点的dis值是错的

另外,在极端链状图中,dijkstra每更新一次都要找最小dis值的点,时间复杂度将大大超过SPFA算法,同样也是不适合的。

5.优化

dijkstra算法的颈瓶在于每次寻找dis值最小的点所消耗的时间。
这时我们不难想到用一个小根堆来维护每个点的dis值,这样就可以高效地插入/寻找,从而降低时间复杂度

6.代码实现(C++)

1.普通dijkstra
洛谷模板传送门

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<string>
using namespace std;
struct Edge//前向星存边
{
    int z;//此边的子节点
    int val;//此边的权值
    int nexty;//与它最近的父节点一样的边的编号
}edge[1000000];
int head[20000];//以某点为父节点引出的最后一条边
int cnt=0;//边编号
inline void add(int a,int b,int c)//存边
{
    cnt++;
    edge[cnt].z=b;
    edge[cnt].val=c;
    edge[cnt].nexty=head[a];
    head[a]=cnt;//更新head
}
int main()
{
    bool visit[20000]={0};//是否作为过起点
    long long dis[20000];//距离
    int n,m,s;
    int a,b,c;
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<=n;i++)dis[i]=2147483647;
    for(int i=0;i<m;i++)
    {
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    int curr=s;
    dis[s]=0;
    long long minn;
    while(!visit[curr])//即搜完整张图
    {
        visit[curr]=true;//已做为过起点
        for(int i=head[curr];i!=0;i=edge[i].nexty)//链式前向星搜边
        {
            if(!visit[edge[i].z]&&dis[edge[i].z]>dis[curr]+edge[i].val)
            dis[edge[i].z]=dis[curr]+edge[i].val;//更新操作
        }
        minn=2147483647;
        for(int i=1;i<=n;i++)
        {
            if(!visit[i]&&minn>dis[i])//取新的最小值
            {
                minn=dis[i];
                curr=i;
            }
        }
    }
    for(int i=1;i<=n;i++)printf("%lld ",dis[i]);
    return 0;
}

2.堆优化dijkstra核心代码(手写堆的大佬请忽视)

void dijstra(int v0)
{
     for(int i=1;i<=n;i++) dis[i]=INF;
     dis[v0]=0;
     priority_queue<Node>q;
     q.push(Node(0,v0));
     while(!q.empty())
     {
         Node u=q.top();
        q.pop();
         if(vis[u.id]) continue;  
        vis[u.id]=1;
         for(int i=head[u.id];i;i=edge[i].next)
         {
             int v=edge[i].to;
             int w=edge[i].dis;
             if(dis[v]>w+u.d) 
               {
                 dis[v]=w+u.d;
                 q.push(Node(dis[v],v));
               }    
         }
     }
}

每一个算法都有它的精髓,学好一个算法并不是简简单单会写模板或者能过几道题,需要你真正理解这个算法内在的原理,这样你才能体会到每一个算法的奇妙,才能透彻理解这个算法。
今天的讲解就到这里了,谢谢大家的支持

愿你心中还有诗和远方

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

袋装猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值