ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

最短路

2021-08-18 15:34:44  阅读:208  来源: 互联网

标签:松弛 dist int 短路 st 负环


目录

前言

最短路算法是图论里最重要且最基础的算法之一(但是他也有很难的题)。

最短路算法的主要难点就是在于建模,建模建好了直接跑模板就行。

Dijkstra————单源最短路

朴素版本

先讲Dijkstra算法的朴素版本。

Dijkstra算法的思路是:

  1. 先把所有的dist[i] [1] 初始化成\(+\infty\) [2],然后把起始点的dist设为\(0\)。
  2. 然后找到一个不在S[3]里且距离S最近的一个点。
  3. 把此点加入S
  4. 更新此点的所有不在S里的邻接点,这个操作就叫松弛。
  5. 循环1~4操作直到。

还有一个点:我们可以保证每次松弛至少可以确定一个点是最短路,所以最多只需要循环n次就可以找出所有点的最短路了。

我们可以看出:Dijkstra是一个基于贪心思想的算法,因为我每次都选的是最短的一条边,更新了也肯定是最短的。然后他的邻接点如果不是最短路那么也会被其他的点来松弛[4]一遍。

代码

所以我们就可以开始写代码了。

int Dijkstra()
{
    memset(dist, 0x3f,sizeof dist);     //0x3f是一个较大的值,所以我们用它来初始化。
    dist[1]=0;   //假设从1开始
    for(int i=1;i<=n;i++)
    {
        int t=-1;
        for(int j=1;j<=n;j++)
            if(!st[j]&&(t==-1||dist[t]>dist[j]))      //找到一个距离S最短的点(用了一个bool数组st来表示是不是在S里面)
                t=j;
        st[t]=true;      //可以保证t这个点一定是起始点到t的最短路了,把t加入S
        for(int j = 1; j <= n; j++)
            dist[j] = min(dist[j], dist[t] + g[t][j]);     //松弛t的邻接点
    }
}
时间复杂度

\(O(n^2)\)

堆优化版本

我们发现找离S最近的是哪一个点可以用优先队列(堆)来实现。

int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, 1});    // 因为pair是先排first、再排second
    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();
        int ver = t.second, distance = t.first;
        if (st[ver]) continue; // 如果此点在st内,就不松弛此点。
        st[ver] = true;  //把ver加入S
        for (int i = h[ver]; i != -1; i = ne[i])   //遍历邻接点
        {
            int j = e[i];
            if (dist[j] > dist[ver] + w[i])
            {
                dist[j] = dist[ver] + w[i];   //松弛
                heap.push({dist[j], j});
            }
        }
    }
    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

然后最短路里会有一种奇妙的环,叫负环。

如图,2、3、4就组成了一个负环
如果用dijkstra算法来跑一个有自环的图,就会一直在这个负环上不停的跑。

因为2、3、4点每次松弛之后,他还有更小的、可以松弛到的最短路,所以我们定义:带有负环的图没有最短路(只有更短路)。

于是我们便引入了一种可以处理负环图的算法:Bellman-Ford算法。

Bellman-Ford————单源最短路

效率极低,但代码极好写。

Bellman-Ford的思路是:不停的进行松弛、每次松弛把每条边都松弛一遍,所以我们能保证每次一条边再也不能松弛了(即成为了最短路),所以只需要松弛n-1次就能求出每一个点的最短路。那如果我们已经松弛了n-1次、如果还能松弛的话,就说明有负环了,因为只有有负环的情况,我才会一直不停的松弛,我们可以从这点的判断此图到底有没有负环。

  1. 先把所有点的dis设成\(+\infty\),然后把起始点的dis设成\(0\)。
  2. 把每条边都松弛一遍,重复n-1次。
  3. 最后判断还能不能松弛,如果还能松弛,就说明有负环,如果不能松弛了,就说明没有负环了。
代码
bool Bellman_Ford()
{
  for(int i = 1; i <= nodenum; ++i) //初始化
    dis[i] = (i == original ? 0 : MAX);
  for(int i = 1; i <= nodenum - 1; ++i)
    for(int j = 1; j <= edgenum; ++j)
      if(dis[edge[j].v] > dis[edge[j].u] + edge[j].cost) //松弛(顺序一定不能反~)
      {
        dis[edge[j].v] = dis[edge[j].u] + edge[j].cost;
        pre[edge[j].v] = edge[j].u;
      }
      bool flag = 1; //判断是否含有负权回路
 
      for(int i = 1; i <= edgenum; ++i)
        if(dis[edge[i].v] > dis[edge[i].u] + edge[i].cost)
        {
          flag = 0;
          break;
        }
        return flag;
}

此代码没有任何用,因为复杂度极高,我们一般用Bellman-Ford的队列优化形态:SPFA算法来判断有无负环。

SPFA————单源最短路

用一个队列来维护节点,容易被菊花图链式图卡掉。

所以做一些缺德考虑周到的lxl出题人的题目时,一定不要用SPFA。

思路:

  1. 先把起始点加入队列,dist[起始点(假设为s)]设为0,st[s] = 1(st表示此点在不在队列里面)。
  2. 找到队列开头的点,然后遍历此点的所有邻接点,如果dis[i[5]] > dist[tmp[6]] + w[i][7],就松弛掉,i点既然更新了,那么dist[i]肯定变小了,所以i点也得松弛一边,然后看i在不在队列里面(st[i] == false),如果在的话就不管,如果不在的话就把i点加入队列,然后把i点的状态设为在队列里。
  3. 循环2直至队列为空

注:如果要求有没有负环的话就用一个cnt数组记录每个点到源点的边数,一个点被更新一次就+1,一旦有点的边数达到了n那就证明存在了负环。

代码

无求负环版:

int spfa()
{
    queue<PII> q;
    memset(dist,0x3f,sizeof dist);
    dist[1] = 0;
    q.push({0, 1});
    st[1] = true;
    while(q.size())
    {
        PII p = q.front();
        q.pop();
        int t = p.se;
        st[t] = false;   //t点被弹出,st[t] = false;
        for(int i = h[t]; i != -1; i = ne[i])     //使用链式前向星存图
        {
            int j = e[i];                      
            if(dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];    //松弛
                if(!st[j])    //判断在不在队列内
                {
                    st[j] = true;
                    q.push({dist[j], j});
                }
            }
        }
    }
    if(dist[n] == 0x3f3f3f3f)
        return -1;
    else
        return dist[n];
}

求负环版:


int spfa() {
    memset(dist, 0x3f, sizeof dist);
    for (int i = 1; i <= n; i++) {
        q.push(i);
        st[i] = true;
    }
    st[1] = true;
    while (q.size()) {
        int t = q.front();
        q.pop();
        st[t] = false;
        for (int i = h[t]; i != -1; i = ne[i]) {
            int j = e[i];
            if (dist[j] > dist[t] + w[i]) {
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;
                if (cnt[j] >= n) return true;  //有负环
                if (!st[j]) {
                    st[j] = true;
                    q.push(j);
                }
            }
        }
    }
    return false;
}

多源最短路

多源最短路————多次单源最短路

你懂我意思吧

Floyd算法

最好写的最短路!

设\(dist[i][j]\)为从i走到j的最短路是多少。

\(dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]\)

代码

    for(int k = 1; k <= n; k++)
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= n; j++)
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);

\[The End \]


  1. 起点到i的最短路。 ↩︎

  2. 写代码的时候可以写作一个较大的数就行了。 ↩︎

  3. 已经求出(确定)了最短路的点的集合。 ↩︎

  4. 其实就是更新最短路的意思。 ↩︎

  5. 队列开头节点的邻接点。 ↩︎

  6. 队列开头节点。 ↩︎

  7. tmp到i的距离(输入的或算出来的,反正不是dist)。 ↩︎

标签:松弛,dist,int,短路,st,负环
来源: https://www.cnblogs.com/Akafuyu/p/15154356.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有