最短路算法:dij、SPFA

dijkstra 复杂度O(n2)

基本思想:最短路:dijkstra算法+路径输出
建边写法:建边——邻接矩阵、vector、链式向前星

  • 邻接矩阵和vector的版本:
    直接贴学长博客了:图论基本知识
  • 邻接表(链式向前星):
#include <iostream>
#include <string.h>
#include<stdio.h>
#include<math.h>
#include<algorithm>
#include<vector>
#include<queue>
typedef long long LL;
using namespace std;
const int manx=1e5+10;
const int INF=0x3f3f3f3f;
int dis[300],vs[300],head[300];
int n,m,cou;
struct node
{
    int e,len,bf;//边的终点、边长、和这个点有相同起点的上一条边
} edge[manx<<2];
void add(int s,int e,int len)
{
    edge[cou]=node{e,len,head[s]};
    head[s]=cou++;
}
void dij(int s,int e)
{
    memset(dis,INF,sizeof(dis));
    memset(vs,0,sizeof(vs));
    vs[s]=1;
    dis[s]=0;
    int nx=s;
    while(!vs[e])
    {
        for(int i=head[nx]; ~i; i=edge[i].bf)
        {
            int nlen=edge[i].len;
            int ne=edge[i].e;
            if(dis[ne]>dis[nx]+nlen)
            {
                dis[ne]=dis[nx]+nlen;
            }
        }
        nx=-1;//标记距离最小的位置
        for(int j=1; j<=n; j++)
        {
            if(!vs[j])//j点没有走过
                if(nx==-1||dis[j]<dis[nx])
                    nx=j;
        }
        if(nx==-1)
            break;
        vs[nx]=1;
    }
}
int main()
{
    int sx,ex,len,s,e;
    while(scanf("%d%d",&n,&m),n||m)
    {
        cou=1;
        memset(head,-1,sizeof(head));
        for(int i=1; i<=m; i++)
        {
            scanf("%d%d%d",&sx,&ex,&len);
            add(sx,ex,len);
            add(ex,sx,len);
        }
        //scanf("%d%d",&s,&e);
        s=1,e=n;
        dij(s,e);
        if(dis[e]==INF)
        {
            if(s==e)
                printf("0\n");
            else
                printf("-1\n");
        }
        else
            printf("%d\n",dis[e]);
    }
}

1、可以思考下这3种写法,分别是怎么遍历完有相同起点的所有边的
2、处理完后,没有路径的顶点的结果是什么
3、为什么dij不能解决边权有负值的情况

优先队列优化dij+链式前向星 复杂度O(mlogn)

m是图的边数,n是顶点数

用优先队列优化后,就可以直接从队首得到下一个顶点,省去了遍历图中每个顶点去找下一个顶点(如果此时与处在队首的顶点已经遍历过了,即vs[pos]=1,因为某个节点可能被放进去很多次,后面的就可以直接跳过)

定义一个结构体,结构体中包含两个数据:顶点和该顶点到起点的距离,然后更新一次就将这个顶点和新的距离值放入队列,设置下结构体的优先级,让距离大的沉到队尾

代码(没有提交过,但是大概写法是这样子得):

#include <iostream>
#include <string.h>
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<queue>
typedef long long LL;
using namespace std;
#define chl (root<<1)
#define chr ((root<<1)|1)
#define mid ((l+r)>>1)
const int manx=1e5+10;
const int INF=0x3f3f3f3f;
int cou=0,head[1010],path[manx][2],dis[1010];
bool vs[1010];
struct node
{
    int e,len,bf;
} edge[manx];
void add(int s,int e,int len)
{
    edge[cou]=node{e,len,head[s]};
    head[s]=cou++;
}
struct poi
{
    int pos,len;
};
bool operator<(poi a,poi b)
{
    return a.len>b.len;
}
int dij(int s,int e)
{
    priority_queue<poi>qu;
    memset(dis,INF,sizeof(dis));
    memset(vs,0,sizeof(vs));
    //vs[s]=1;
    dis[s]=0;
    int nx=s;
    qu.push(poi{s,0});
    while(!qu.empty())
    {
        nx=qu.top().pos;
        qu.pop();
        //if(vs[nx])
        //    continue;
        vs[nx]=1;
        if(vs[e])
            break;
        for(int i=head[nx]; ~i; i=edge[i].bf)
        {
            int nlen=edge[i].len;
            int ne=edge[i].e;
            if(dis[ne]>dis[nx]+nlen)
            {
                dis[ne]=dis[nx]+nlen;
                qu.push(poi{ne,dis[ne]});
            }
        }
    }
    return dis[e];
}
int main()
{
    int n,m,s,e,ans,a,b,len;
    memset(head,-1,sizeof(head));
    scanf("%d%d%d%d",&n,&m,&s,&e);
    for(int i=1; i<=m; i++)
    {
        scanf("%d%d%d",&a,&b,&len);
        path[i][0]=a;
        path[i][1]=b;
        add(a,b,len);
        add(b,a,len);
    }
    ans=dij(s,e);
    printf("%d\n",ans);
}
/*
5 5 1 5
1 2 2
2 3 2
2 4 2
3 5 100
4 5 2*/

例题:2872: NO GAME NO LIFE

SPFA 期望复杂度O(kE)

E是图的边数,k是一个常数很多情况下k不超过2(不包含有可到达负环的情况)

可以解决边权有负值的,最长路等等dij无法处理的。

链式向前星版本:

#include <iostream>
#include <string.h>
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<queue>
typedef long long LL;
using namespace std;
const int manx=1e5+10;
const int INF=0x3f3f3f3f;
int dis[300],vs[300],head[300];
int n,m,cou;
struct node
{
    int e,len,bf;//边的终点、边长、和这个点有相同起点的上一条边
}edge[manx<<2];
void add(int s,int e,int len)
{
     edge[cou]=node{e,len,head[s]};
     head[s]=cou++;
}
vector<node>vt[300];
int spfa(int s)
{
    int c[300];
    queue<int>qu;
    memset(c,0,sizeof(c));
    memset(dis,INF,sizeof(dis));
    memset(vs,0,sizeof(vs));
    dis[s]=0;
    vs[s]=1;
    c[s]=1;
    //c数组判断是否有负环(并且有从源点到负环的路径),即c[s]是否大于n
    qu.push(s);
    int sx;
    while(!qu.empty())
    {
        sx=qu.front();//边的起点
        qu.pop();
        for(int i=head[sx];i;i=edge[i].bf)//i表示边的序号
        {
             int nx=edge[i].e;
             int nlen=edge[i].len;
             if(dis[sx]+nlen<dis[nx])
             {
                  dis[nx]=dis[sx]+nlen;
                  if(!vs[nx])
                  {
                       qu.push(nx);
                       vs[nx]=1;
                       c[nx]++;//nx被放入队列的次数
                       if(c[nx]>n)
                         return 0;
                  }
             }
        }
        vs[sx]=0;
    }
    return 1;
}
int main()
{
    int sx,ex,len,s,e;
    while(scanf("%d%d",&n,&m),n||m)
    {
        cou=1;
        memset(head,0,sizeof(head));
        for(int i=1; i<=m; i++)
        {
            scanf("%d%d%d",&sx,&ex,&len);
            add(sx,ex,len);
            add(ex,sx,len);
        }
        //scanf("%d%d",&s,&e);
        s=1,e=n;
        int flag=spfa(s);
        if(!flag)
          continue;
        if(dis[e]==INF)
        {
            if(s==e)
                printf("0\n");
            else
                printf("-1\n");
        }
        else
            printf("%d\n",dis[e]);
    }
}
发布了52 篇原创文章 · 获赞 26 · 访问量 3174

猜你喜欢

转载自blog.csdn.net/qq_43803508/article/details/96425507