第三章 搜索与图论(二)(最短路)

这篇具有很好参考价值的文章主要介绍了第三章 搜索与图论(二)(最短路)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、最短路问题

1、对于稠密图,由于朴素版的dijkstra算法与边数无关使用这种算法的复杂度较低。稀疏图用堆优化版的算法;单源最短路中存在负权边用SPFA 算法通常较好;多源用floyd算法;

第三章 搜索与图论(二)(最短路),蓝桥准备,图论,算法

 难点:如何建图,抽象为最短路问题。

二、朴素版dijkstra算法

由于稠密图用这种算法,邻接矩阵存图,注意把g初始化为0x3f;st保存每个数组的状态,

#include<bits/stdc++.h>
//849dijkstra最短路
using namespace std;
const int N=510;
int g[N][N],disk[N],st[N];
int n,m;
int dijkstra()
{
   disk[1]=0;
   for(int i=1;i<=n;i++)
   {
       int t=-1;//找最小的那个
       for(int j=1;j<=n;j++)
          if(st[j]==-1&&(disk[t]>disk[j]||t==-1))t=j;
       st[t]=1;//标记
       for(int j=1;j<=n;j++)
          disk[j]=min(disk[j],disk[t]+g[t][j]);



   }
   if(disk[n]==0x3f3f3f3f)return -1;
   return disk[n];
}
int main()
{
    cin>>n>>m;
    memset(disk,0x3f,sizeof disk);//初始化最短路为正无穷
    memset(g,0x3f,sizeof g);
    memset(st,-1,sizeof st);
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        //由于存在重复带权边,所以要留住那个较短的边因此初始化g为无穷
        g[a][b]=min(g[a][b],c);
    }
    int t=dijkstra();
    cout<<t<<endl;
    return 0;
}

三、堆优化版的dijkstra算法

在获取disk最小的节点的位置进行优化。用堆来存储起点到目标节点的距离。又因为算法每次更新边,更新一次堆的时间复杂度为log(n),时间复杂为O(m log(n))

直接使用优先队列实现堆,优先队列中的元素为pair元素,first为路径长度,second为端点名字。

#include<bits/stdc++.h>
//850dijkstra最短路(堆优化)邻接表存图
using namespace std;
const int N=1e5+10;
typedef pair<int,int> PII;
int h[N],e[N],ne[N],idx,w[N];
int disk[N],st[N];
int n,m;
void add(int a,int b,int c)//将边和权重保存到邻接表中
{
    e[idx]=b;ne[idx]=h[a];
    w[idx]=c,h[a]=idx++;
}
int dijkstra()
{
   disk[1]=0;
   priority_queue<PII,vector<PII>,greater<PII>>heap;//小顶堆
   heap.push({0,1});
   while(heap.size())
   {
       auto t=heap.top();//拿出堆顶
       heap.pop();
       int ver=t.second,distance=t.first;
       if(st[ver]==1) continue;//已经确定过最短路继续
       st[ver]=1;
       for(int i=h[ver];i!=-1;i=ne[i])//遍历与其相连的所有边
       {
           int j=e[i];
           if(disk[j]>distance+w[i])
           {
           //更新后的disk可能不是最小值
           //这些没有用的中间值即使跑到堆顶取出来了
           //但是只要发现对应的点已经确定最短路了就放弃
               disk[j]=distance+w[i];
               heap.push({disk[j],j});
           }
       }
   }


   if(disk[n]==0x3f3f3f3f)return -1;
   return disk[n];
}
int main()
{
    cin>>n>>m;
    memset(disk,0x3f,sizeof disk);//初始化最短路为正无穷
    memset(st,-1,sizeof st);
    memset(h,-1,sizeof h);
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
    }
    int t=dijkstra();
    cout<<t<<endl;
    return 0;
}

三、  Bellman_Ford算法(用于求有边数限制的最短路)

可以用于用来找负环,或者有最多经过k条边的限制的题目。

而且即使有负环让你求最短路,有k条边的限制也不会出现负无穷的结果

算法:进行指定次数的循环,每次循环都遍历所有边

#include<bits/stdc++.h>
// 853. 有边数限制的最短路 (bellman_ford)
using namespace std;
const int N=510,M=10010;
int n,m,k;
int dist[N],backup[N];
struct Edege//结构体保存边,便于遍历
{
    int a,b,c;
}edges[M];

int bellman_ford()
{
    memset(dist,0x3f,sizeof dist);//初始化
    dist[1]=0;
    for(int i=0;i<k;i++)
    {
        memcpy(backup,dist,sizeof dist);//复制上一次的结果到backup,
        //防止发生串性地更新
        for(int i=0;i<m;i++)
        {
            int a=edges[i].a,b=edges[i].b,w=edges[i].c;
            dist[b]=min(dist[b],backup[a]+w);
        }
    }
    //由于存在负边,可能会小于0x3f3f3f3f,根据题目的数据范围,计算最多减的次数
    if(dist[n]>=0x3f3f3f3f/2)return -1;
    else return dist[n];
}
int main()
{
    cin>>n>>m>>k;
    for(int i=0;i<m;i++)
    {
       int a,b,w;
       cin>>a>>b>>w;
       edges[i]={a,b,w};//保存边
    }
    int t=bellman_ford();
    if(t==-1)puts("impossible");
    else cout<<t<<endl;
    return 0;

}

四、SPFA算法求最短路

SPFA算法其实就是在Bellman-ford算法基础上的优化。Bellman-ford算法看起来比较傻,每次迭代的话是遍历所有边来更新,但是每次迭代的话不是每条边都会更新。SPFA算法就是对这个做优化,每次迭代dist[b]可以更新的话,一定是dist[a]变小了,因为如果dist[a]不变的话,dist[b]一定不变。只有dist[a]变小了,它的后继才会变小。所以SPFA算法就从这个点进行优化。

SPFA算法的思路就是迭代的时候用一个队列来做,队列里面存的就是到起点距离变小的点。先把起点放到队列里面去,只要队列不空,也就是队列里面还有距离变小的点的话,就执行一下操作:

先取出队头t,然后队头出队
更新t的所有出边,t到起点的距离不是变小了吗, 那么所有和t相连的点都有可能变小,如果更新成功的话,就入队。但是注意要判断一下这个点已经入过队的话就不用重复加入了。

————————————————           
原文链接:https://blog.csdn.net/qq_52905520/article/details/126574584

例题:利用spfa算法求最短路,从队列头拿出结点,只要能更新就更新,并把更新的点加入到队列中,并利用st bool数组保存队列中是否已经有当前结点。

spfa算法中不使用backup的原因是每次更新只更新邻边,不会出现串联更新。

#include<bits/stdc++.h>
//
using namespace std;
const int N=1e5+10;
int h[N],e[N],ne[N],idx,w[N];
int dist[N];
bool st[N];
int n,m;
void add(int a,int b,int c)
{
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;

}
int spfa()
{
    memset(dist,0x3f,sizeof dist);
    memset(st,false,sizeof st);
    dist[1]=0;
    queue<int>q;//队列存所有更新过的点
    dist[1]=0;
    q.push(1);//入队
    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];//先更新
            if(!st[j])//只有队列里面没有的时候才能入队
            {
                q.push(j);
                st[j]=true;
            }
          }
        }
    }
    if(dist[n]==0x3f3f3f3f)return -1;
    return dist[n];
}
int main()
{
  cin>>n>>m;
  memset(h,-1,sizeof h);
  while(m--)
  {
      int a,b,c;
      cin>>a>>b>>c;
      add(a,b,c);
  }
  int t=spfa();
  if(t==-1)puts("impossible");
  else cout<<t<<endl;
  return 0;
}

五、spfa算法判断是否有负环

通过最短路的边数来判断是否有负环 。

#include<bits/stdc++.h>
//852 spfa判断是否
using namespace std;
const int N=1e5+10;
int h[N],e[N],ne[N],idx,w[N];
int dist[N];
bool st[N];
int cnt[N];//记录边数
int n,m;
void add(int a,int b,int c)
{
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;

}
int spfa()
{
    memset(dist,0x3f,sizeof dist);
    memset(st,false,sizeof st);
    dist[1]=0;
    queue<int>q;//队列存所有更新过的点
    //因为有可能负环从第一个结点到不了所以把所有结点放到队列
    for(int i=1;i<=n;i++)q.push(i),st[i]=true;
    dist[1]=0;

    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 1;
            if(!st[j])//只有队列里面没有的时候才能入队
            {
                q.push(j);
                st[j]=true;
            }
          }
        }
    }
    return 0;
}
int main()
{
  cin>>n>>m;
  memset(h,-1,sizeof h);
  while(m--)
  {
      int a,b,c;
      cin>>a>>b>>c;
      add(a,b,c);
  }
  int t=spfa();
  if(spfa())puts("Yes");
  else puts("No");
}

六、弗洛伊德Floyd算法求多源最短路

三重循环文章来源地址https://www.toymoban.com/news/detail-831809.html

#include<bits/stdc++.h>
//852 spfa判断是否
using namespace std;
const int N=210,INF=1e9;
int d[N][N];
int n,m,Q;
void floyd()
{

    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]);
}
int main()
{
    cin>>n>>m>>Q;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(i==j)d[i][j]=0;
            else d[i][j]=INF;
        }
    }
    while(m--)
    {
       int a,b,c;
       cin>>a>>b>>c;
       d[a][b]=min(d[a][b],c);
    }
    floyd();
    while(Q--)
    {
        int a,b;
        cin>>a>>b;
        if(d[a][b]<INF/2) cout<<d[a][b]<<endl;
        else cout<<"imposible"<<endl;
    }

}

到了这里,关于第三章 搜索与图论(二)(最短路)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 第三章 图论 No.2单源最短路之虚拟源点,状压最短路与最短路次短路条数

    dp是特殊的最短路,是无环图(拓扑图)上的最短路问题 1137. 选择最佳线路 1137. 选择最佳线路 - AcWing题库 对于每组测试数据,该重置的数据要重置,我没有重置idx,导致TLE 处理反向建图,还有一种扩展做法:虚拟源点 设置虚拟源点,与每个起点之间连接边权为0的边 原问题

    2024年02月14日
    浏览(49)
  • 第三章 图论 No.3 flody之多源汇最短路,传递闭包,最小环与倍增

    flody的四个应用: 多源汇最短路 传递闭包 找最小环 恰好经过k条边的最短路 倍增 多源汇最短路:1125. 牛的旅行 1125. 牛的旅行 - AcWing题库 直径概念:同一连通块中,两个距离最远的点之间的距离 如何求直径?由于图中存在着两个连通块,所以直接对全图做一个flody,就能更

    2024年02月14日
    浏览(45)
  • 搜索与图论第六期 最短路问题

    Dijkstra算法是一种著名的图算法,主要用于求解有权图中的单源最短路径问题。它由荷兰计算机科学家艾兹赫尔·戴克斯特拉(Edsger Wybe Dijkstra)在1956年首次提出。Dijkstra算法的核心思想是通过以下步骤逐步构建最短路径树: 初始化:创建一个空白的最短路径字典,其中每

    2024年02月20日
    浏览(42)
  • 第三章 图论 No.13拓扑排序

    拓扑序和DAG有向无环图联系在一起,通常用于最短/长路的线性求解 裸题:1191. 家谱树 1191. 家谱树 - AcWing题库 差分约束+拓扑排序:1192. 奖金 1192. 奖金 - AcWing题库 由于图中所有边权都是正数,可以直接使用topsort求解差分约束问题 根据题意,要求一个最小值,使用最长路求解

    2024年02月12日
    浏览(45)
  • C++算法之旅、06 基础篇 | 第三章 图论

    常用代码模板3——搜索与图论 - AcWing 尽可能往深处搜,遇到叶子节点(无路可走)回溯, 恢复现场继续走 数据结构:stack 空间:需要记住路径上的点, (O(h)) 。 ⭐ BFS使用空间少; 无最短路 性质 每个DFS一定对应一个 搜索树 ;要考虑用什么 顺序 遍历所有方案;DFS就是递

    2024年02月10日
    浏览(45)
  • 第三章 图论 No.12欧拉回路与欧拉路径

    小学一笔画问题,每条边只经过一次 判断图是否存在欧拉回路:判断图是否连通(存在孤立边),再根据有向/无向具体判断 对于无向图来说, 欧拉路径 中,起点和终点的度数为奇数,中间点的度数为偶数 起点和终点:开始和结束时必须经过一条边,其余情况为:从一条边

    2024年02月12日
    浏览(41)
  • 第三章 图论 No.4最小生成树的简单应用

    存在边权为负的情况下,无法求最小生成树 裸题:1140. 最短网络 1140. 最短网络 - AcWing题库 套个prim的板子即可 裸题:1141. 局域网 1141. 局域网 - AcWing题库 裸题,稀疏图,套个kruskal的板子就行 需要注意的是:题目给定的图可能存在多个连通块,若使用prim算法,需要对每个连通

    2024年02月14日
    浏览(57)
  • 第三章 图论 No.10无向图的双连通分量

    无向图有两种双连通分量 边双连通分量,e-DCC 点双连通分量,v-DCC 桥:删除这条无向边后,图变得不连通,这条边被称为桥 边双连通分量:极大的不含有桥的连通区域,说明无论删除e-DCC中的哪条边,e-DCC依旧连通 (该连通分量的任意边属于原图中的某条环)。此外,任意两

    2024年02月12日
    浏览(39)
  • 第三章 图论 No.11二分图,匈牙利算法与点覆盖

    257. 关押罪犯 - AcWing题库 最大最小问题,一眼二分 答案的范围在 [ 1 , 1 e 9 ] [1, 1e9] [ 1 , 1 e 9 ] 之间,二分答案,check(mid) check:将所有权值大于mid的边进行二分,若能得到二分图,返回true,否则返回false 最终将得到最优解ans,所有大于ans的边组成的图为二分图,若是图中有边

    2024年02月12日
    浏览(41)
  • 第三章 图论 No.8最近公共祖先lca, tarjan与次小生成树

    O ( m l o g n ) O(mlogn) O ( m l o g n ) ,n为节点数量,m为询问次数,lca是一种在线处理询问的算法 自己也是自己的祖先 倍增: f a ( i , j ) fa(i, j) f a ( i , j ) 表示从i开始,向上走 2 j 2^j 2 j 步走到的点 j = 0,走到父节点 j 0,分两步走,先走到 2 j − 1 2^{j-1} 2 j − 1 步再走 2 j − 1 2^{

    2024年02月13日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包