spfa

spfa:能解决单源最短路的问题,它是Bellman-ford的队列优化,复杂度最好情况为O(M)所以在某些情况下可以解决优化的dijkstra,并且能够处理负边以及负权环的问题,但在稠密图中SPFA算法时间复杂度在网状情况会退化到(NM)。
Bellman-ford:是每次仅对最短路程发生变化了的点的相邻边进行松弛操作,但是由于不知道哪些点的最短路发生了变化,所以要对每条边都进行一次循环,并且因为在n个点的图中,任意两点的最短路径最多包含n-1条边,还要检查每条边是否对当前顶点能够进行松弛,所以复杂度为O(n*m)。

spfa:只对发生了变化的点进行压缩,对这个点进行入队操作,将这个点的出边进行松弛操作即可。

具体操作步骤:
1,将源点压入队列
2,取出队头元素,对该元素的各邻接边进行松弛操作。如果该点没有在队列中,就压入队列
3,重复步骤 1.

  • 这里更新一种最简洁快速的存图方式:用数组来进行模拟
int head[N],e[N],ne[N],idx; //数组存储图四件套。
//head表示每个以每个顶点的头,e存储每个节点的数据,ne存储每个节点下一个数据的下标,idx表示当前是第几个节点
 
存图
memset(head,-1,sizeof head);//给head赋初值-1,表示头结点为空
void add()
{  int a,b;
    cin>>a>>b;    //a,b表示一条a->b的边
    e[idx]=b;   //  将出边顶点存入e[]中第idx个结点
    ne[idx]=head[a];  //第ixd个结点的指针域都是指向头的地址
    head[a]=idx++;   //头以前插法保留当前节点的下标
  
}
遍历
 for(int i=head[x];i!=-1;i=ne[i])  //每次都用i保存head[]的下标,这里就是指向以x为头的下标
             {
                 int y=e[i];  //取出第i个节点的数据放入y中
                 in[y]--;
                 if(in[y]==0)
                   q.push(y);
             }

- 链式向前星

  • 数据结构
struct node    //链式向前星的结构
{
    int to,w,next;  //终点、权值、以及模拟的next指针
} edge[N];
  • 构造:
void add()    //链式向前星创建邻接表
{
    memset(head,-1,sizeof(head));//将每个头顶点都设置为-1表示空
    for(int i=0; i<m; i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        edge[i].to=b;//终点
        edge[i].w=c;  //权值
        edge[i].next=head[a];  //以头插法插入节点
        head[a]=i;   //头指向第一个节点,保留它为第几个(下标)
    }

}
  • 遍历:for(int i=head[t]; i!=-1; i=edge[i].next) { cout<<edge[i].to<<edge[i].w; }

851. spfa求最短路

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N=1e5+10;
int head[N],dist[N],book[N];
int n,m;
queue<int >q;
struct node    //链式向前星的结构
{
    int to,w,next;  //终点、权值、以及模拟的next指针
} edge[N];
void add()    //链式向前星创建邻接表
{
    memset(head,-1,sizeof(head));//将每个头顶点都设置为-1表示空
    for(int i=0; i<m; i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        edge[i].to=b;//终点
        edge[i].w=c;  //权值
        edge[i].next=head[a];  //以头插法插入节点
        head[a]=i;   //头指向第一个节点
    }

}
int main()
{
    cin>>n>>m;

    add();

    memset(dist,0x3f,sizeof(dist));

    dist[1]=0;
    q.push(1);
    book[1]=1;
    while(q.size())
    {
        int t=q.front();   //每次弹出一个元素
        q.pop();
        book[t]=0;    //弹出的元素标记没在队列内出现过
        for(int i=head[t]; i!=-1; i=edge[i].next)  //对以t为头的链表进行遍历
        {
            int j=edge[i].to;
            int w=edge[i].w;
            if(dist[j]>dist[t]+w)   //如果能够压缩1到j的距离就压缩
            {
                dist[j]=dist[t]+w;
                if(book[j]==0)//并且在压缩完结点j,说明j结点的距离发生了变化,如果它不在队中就将它入队
                {
                    q.push(j);
                    book[j]=1;
                }
            }
        }
    }

    if(dist[n]==0x3f3f3f3f)
        cout <<"impossible";
    else
        cout <<dist[n];
}

852. spfa判断负环

  1. 首先将1~n个顶点都入队,因为题目不是说从顶点1开始到顶点n查找负权环,而是在每个顶点都可能存在负权环。

  2. 定义一个cnt[x]数组来存储每个顶点 int cnt[N]; //每个顶点的边数

  3. cnt[x]:1~x出现的边数,如果出现的边数==n了,就说明到x这个顶点出现了n+1个顶点,在这n个顶点必定有两个顶点是相同的,而我们每次判断负权环是在能够压缩路径的前提下(也就是有更短距离,距离变小的情况下才压缩、判断,所以只会是更小的负权才压缩)这样就存在了一个负权环。

  4. if(dist[v]>dist[u]+w) { dist[v]=dist[u]+w; cnt[v]=cnt[u]+1; //对出边顶点的边数+1 if(cnt[v]>=n) //如果1~v这个顶点的边数==n,说明有n+1个顶点 { flag=0; break; }

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N=1e5+10;
int n,m,book[N],dist[N],head[N];
int cnt[N];   //每个顶点的边数
struct node
{
    int to,w,next;
}edge[N];
void add()
{  memset(head,-1,sizeof(head));
    for(int i=0;i<m;i++)
     { int a,b,c;
         cin>>a>>b>>c;
          edge[i].to=b;
          edge[i].w=c;
          edge[i].next=head[a];
          head[a]=i;
     }
}
int main()
{
    cin>>n>>m;
    int flag=1;
    add();
  
    queue<int >q;
    for(int i=1;i<=n;i++)
    {
         q.push(i);
          book[i]=1;

    }

     while(q.size())
     {
          int u=q.front();
            q.pop();
          book[u]=0;
           for(int i=head[u];i!=-1;i=edge[i].next)
           {      int v=edge[i].to,w=edge[i].w;
                  if(dist[v]>dist[u]+w)
                   {
                        dist[v]=dist[u]+w;
                          cnt[v]=cnt[u]+1;   //对出边顶点的边数+1
                           if(cnt[v]>=n)   //如果1~v这个顶点的边数==n,说明有n+1个顶点
                           {
                               flag=0;
                               break;
                           }
                          if(book[v]==0)
                           {
                                q.push(v);
                                book[v]=1;
                           }
                   }
           }
  if(flag==0)
    break;
     }
      if(flag==0)
      cout <<"Yes";
      else cout <<"No";

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值