单源最短路径

目录

无负权单源最短路径

迪杰斯特拉算法(dijkstra)

朴素版迪杰斯特拉

小根堆优化版本dijkstra

有负权的图的单源最短路径

SPFA

总结


无负权单源最短路径

    

        在处理图论相关问题时,经常会遇到求一点到其他点的最短距离是多少的问题,很多实际应用场景的题目也可以转化成求最短路的问题,这里我们先来了解没有负权的图的最短路问题.

迪杰斯特拉算法(dijkstra)

        迪杰斯特拉算法是由dijkstra提出的,它的主要思想就是:先设一个数组存储起点到每个点的距离并且初始化为无穷大,从起点出发,遍历与当前点相连的所有点,如果当前点到相连点的距离小于数组中对应的值,就更新这个值,遍历完所有与当前点相连的点后把当前点打个标记,代表已经确定,同时进行下一轮循环,寻找未确定的所有点中距离起点最近的一个点当作起点重复开头的操作,直到所有点都确定完毕,这样子距离数组中的值就是距离起点的最短路程。

        这样子干说很抽象,具体看以下代码吧:

朴素版迪杰斯特拉

void dijkstra(int start)
{
  
  memset(dis,0x3f3f3f3f,sizeof(dis));
  memset(vis,0,sizeof(vis));
  dis[start]=0;

  for(int i=0;idis[x]+e[i].val)
      {
        dis[y]=dis[x]+e[i].val;
      }
    }
  }

}

         上面这个方法实现的算法有一个缺陷就是每次需要遍历一遍去找距离起点最近的未被访问过的点,很浪费时间,每次都要跑一边全部点,所以我更喜欢小根堆优化版本的迪杰斯特拉

        小根堆就是C++stl中的一种数据结构,它里面的数据会自动按特定的顺序排序,这里我们使用小根堆来存放点和点的距离,每次取堆顶的数据即可,小根堆的底层实现是一颗有序的二叉树,它的查询操作效率是比你遍历一边数组要快的多的。

小根堆优化版本dijkstra

注意:我定义的虽然是大根堆,但是我在结构体定义中重载了<这个运算符,具体看以下代码

#define N 10010
struct node{
  int cnt;//起点号
  int val;//存放距离起点的距离
  bool operator < (const node&x)const {//重载小于号
    return val>x.val;
  }
};

struct edge{
  int ed;//终点
  int val;//边权
  int nt;//当前起点的下一个边的序列号
}e[N];//链式向前星存储边

int head[N],dis[N];//head存储每个点的最新录入的一条边在数组e中的序列号
int n,m;
bool vis[N];

void dijkstra(int start)
{
  priority_queue heap;//小根堆:堆顶为权值最小的边
  memset(dis,0x3f3f3f3f,sizeof(dis));//初始化距离为无限大
  memset(vis,0,sizeof(vis));//vis数组用来判断一个点是否访问过
  dis[start]=0;//起点的距离设置为0

  heap.push((node){start,dis[start]});//起点入小根堆

  while(heap.size())
  {
    int x=heap.top().cnt;//取小根堆顶的顶点号

    if(vis[x])
      continue;

    vis[x]=1;//标记x点

    for(int i=head[x];i!=-1;i=e[i].nt)//链式向前星遍历所有相连的点
    {
      int y=e[i].ed;

      if(dis[y]>dis[x]+e[i].val)//如果从x点出发到y点的距离比当前y点距离起点更近,更新距离
      {
        dis[y]=dis[x]+e[i].val;
        heap.push((node){y,dis[y]});//将更新后的点的数据加入小根堆

      }
    }
  }

}

有负权的图的单源最短路径

SPFA

        迪杰斯特拉算法是解决单源最短路径的一个非常好的方法,但是当图中出现负权的时候就没法求出最短路了,说实话我觉得在图中搞一个负权是没有太多实际意义的,像实际应用在地图导航等等软件中,两点间的距离怎么可能是负值呢?除非是后台出bug了,迪杰斯特拉肯定也没想到会有负权这么个东西。当然吐槽归吐槽,一些算法题还是有负权的,我们需要解决这些问题,SPFA算法恰好解决了这个问题。spfa的英文名是Bellman-Ford using queue optimization

        看英文名字就知道这是用队列优化后的贝尔曼福特算法

        推荐各位先去学习一下原版算法再来看优化版本,这里由于时间篇幅原因就不再赘述

        该算法的具体思想就是:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。

        松弛操作是指对于每个顶点v∈V,都设置一个属性d[v],用来描述从源点s到v的最短路径上权值的上界,称为最短路径估计

        下面上代码,存储边同样是使用了链式向前星

void SPFA(int start)
{
  queue q;
  memset(vis,0,sizeof(vis));
  memset(dis,0x3f3f3f3f,sizeof(dis));

  dis[start]=0;
  q.push(start);
  vis[start]=1;//入队列
  while(q.size())
  {
    int x=q.front();
    vis[x]=0;//取出队头元素,出队列

    for(int i=head[x];i!=-1;i=e[i].nt)//松弛操作
    {

      int y=e[i].ed;

      if(dis[y]>dis[x]+e[i].val)//有点的距离需要更新
      {
        dis[y]=dis[x]+e[i].val;
        if(vis[x]==0)
        {
          q.push(y);//入队列
          vis[y]=1;
        }
      }
    }
  }
}

通过代码可以看出 它其实比迪杰斯特拉要低效,这就是处理负权边的时间代价

总结

        今天回顾了单源最短路径,还是首推迪杰斯特拉算法,非常好用的一个算法,当然涉及到负权就要利用spfa了,说到单元最短,那就离不开多源最短路径,我会在后续文章中更新多源最短路的算法。

你可能感兴趣的:(数据结构与算法,算法,图论)