【图论】最短路问题之spfa

写在算法前面:

前向星存图(一个神奇的超越邻接矩阵的存在)

首先讲一下需要定义的一些东西??

1.head数组:head[点数]:head[i]表示以当前点i为起点的最后一条边(这里的最后指的是编号【我们按输入顺序给边编一个号】)。

这个图即为head[1]=4,表示以1为起点的边的最后一条是点1—>点5编号为4的边;

2.num_edge,表示边的总个数;

3.结构体:

struct Edge{
    int next,to,dis;
}edge[边数];

这里,edge[i].next表示上一条以i为起点的边:

还是上面那个图,这里edge[4].next=3;

edge[i].to表示这条边i的终点;

edge[i].dis表示这条边的权值;

存图
void addedge(int from,int to,int dis){
    num_edge++;//因为存入一条边,总边数+1;
    edge[num_edge].next=head[from];//新存入一条以from为起点的边,之前的以from为起点的最后一条边变成了新边的上一条边
    edge[num_edge].to=to;//存终点
    edge[num_edge].dis=dis;//存权值
    head[from]=num_edge;//存入一条以from为起点的边,那么现在以from为起点的最后一条边就是新存入的边
}

提醒:如果要存的是无向图,进行程序时应该:addedge(from,to,dis),addedge(to,from,dis)各跑一遍,所开空间也要*2;

大概是讲完了链式前向星存图;

spfa

算法思想:

1.运用队列的思想,先把起点入队,更新起点能够到达的点,更新这些点到起点的伪最短路(因为可能还有更短情况)然后把得到更新的点中不在队列里的加入队列,以便更新其他点。

2.当前更新的是伪最短路即可能会有更优情况

eg:起点是1,1到6距离为7,加入队列dis[6]=7,而1到7距离为2,7到6距离为3,会将dis[6]更新为2+3=5。所以一遍一遍查,最后将会是最短路。

时间复杂度大约是O(ke),稀疏图中k约等于2,但是毒瘤数据会把复杂度卡成O(nm);

spfa在存图基础上再开vis[点数]查询某个点是否已经在队列里,避免重复入队(请注意,是是否在队列里 队列里 队列里)

dis[点数]用来存某个点到起点的最短路;

以洛谷p3371【模板】单源最短路径(弱化版) 为例纸(标准的过不去qwq)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>

using namespace std;

const int inf=2147483647;//题目要求的初始值

queue <int> q;//定义一个队列q
int n,m,s;
int head[10010],num;
int vis[10010],dis[10010];

struct Edge{
    int next,to,dis;
}edge[500010];

void addedge(int from,int to,int dis){//加边
    num++;
    edge[num].next=head[from];
    edge[num].to=to;
    edge[num].dis=dis;
    head[from]=num;
}

void spfa(){
    for(int i=1;i<=n;i++){//首先初始化,将所有点的dis赋值为2147483647,所有点都不在队列里,vis为0
        dis[i]=inf;
        vis[i]=0;
    }
    q.push(s);//把起点入队
    dis[s]=0;//起点到起点,最短路为0
    vis[s]=1;//入队了,vis变为1
    while(!q.empty()){//开始循环
        int u=q.front();
        q.pop();//把队首元素出队
        vis[u]=0;//出队了,vis重置为0(因为可能还有更近的下次还能循环到)
        for(int i=head[u];i;i=edge[i].next){//遍历以u为起点的所有遍
            int v=edge[i].to;//v为终点
            if(dis[v]>dis[u]+edge[i].dis){//如果之前的最短路比由u点出发再”拐弯“的点的要大,显然他不是最短,更新他
                dis[v]=dis[u]+edge[i].dis;
                if(!vis[v]){//如果终点不在队列里,入队
                    q.push(v);
                    vis[v]=1;
                }
            }
        }
    }
}

int main(){
    scanf("%d%d%d",&n,&m,&s);
    int u,v,w;
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&u,&v,&w);//输入起点终点权值
        addedge(u,v,w);//加边
    }
    spfa();//核心
    for(int i=1;i<=n;++i)
    {
        if(i==s) printf("0 ");
        else printf("%d ",dis[i]);
    }
    return 0;
}

end-

原文地址:https://www.cnblogs.com/zhuier-xquan/p/10764441.html