USACO butter Dijstra优化算法和SPFA解法

本文通过实例解析,对比了使用邻接矩阵与邻接表在Dijkstra算法中的性能差异,发现邻接表能显著提高算法效率。同时,文章深入探讨了Dijkstra与SPFA两种最短路径算法的区别,指出SPFA在某些情况下更胜一筹。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意是给你一个图,找出一个点使得到其他点的最短路径和最小,返回这个最小值

     第一次觉得是个多源最短路,写了Floyd,超时!

     第二次看了正解:枚举每个点,求一次单源最短路,用朴素Dijkstra,超时!

     然后学习STL,立志学会优先队列,终于做好了堆优化,但是还是超时!

     原来存图用的是邻接矩阵,莫名其妙就会慢(question1/难道是因为每次也要便利所有节点进行松弛的原因吗?求大神解答)

     后来改成邻接表存图,终于,,,他过了,他快速的过了……

    曾一度想改成SPFA ,却到现在还是迷惑于两者的本质区别(question2/难道是vis数组在Dijkstra里用于记录是否松驰过这个节点的边,而在SPFA里用于记录本次松驰过的点是否在队列中,以便决定是否进队?再次求大神解答)

    对比两种算法的运行时间,果然还是SPFA faster一点

1/Dijkstra+priority_queue

/*
ID: 15659801
LANG: C++
TASK: butter
*/
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

#define OPENFILE(s) freopen(#s".in","r",stdin);freopen(#s".out","w",stdout);//宏定义
#define CLOSEFILE fclose(stdin);fclose(stdout);
#define ll long long
#define inf 1e9+7

using namespace std;
struct Node//定义优先队列,{节点,最小距离},运算符重载实现最小优先队列(默认最大优先队列)
{
    int x,d;
    int operator<(const Node& r)const
    {
        return d>r.d;
    }
} ;
struct sss   //邻接表存图(用邻接矩阵会TLE)
{
    int x;
    int w;
    sss *next;
}gra[801],mem[1501*2];//静态预存节点
int cnt=-1;

int n,p,c;
int phc[801];//记录牧场有几头牛
int dis[801],vis[801];

void creat_edge(int fr,int to,int we)//邻接表存图
{
    struct sss *p=&mem[++cnt];
    p->x=to;
    p->w=we;
    p->next=gra[fr].next;
    gra[fr].next=p;
}

void scanff()
{
    cin>>n>>p>>c;
    int i,x;
    int fr,to,we;
    for(i=0; i<n; ++i)
    {
        cin>>x;
        phc[x]++;
    }
    for(i=0;i<=p;++i)//预处理邻接表表头
    {
        gra[i].w=0;
        gra[i].next=NULL;
    }
    for(i=0; i<c; ++i)
    {
        cin>>fr>>to>>we;
        creat_edge(fr,to,we);//存图时注意是有向图还是无向图!!!!
        creat_edge(to,fr,we);
    }
}

int dijkstra(int s)
{
    for(int i=1; i<=p; ++i)
        dis[i]=inf;
    memset(vis,0,sizeof(vis));//vis的意义等待大神们的官方解答
    dis[s]=0;
    
    priority_queue<Node > pq; //定义优先队列
    pq.push((Node){s,dis[s]});
    
    while(!pq.empty())//Dijkstra核心算法
    {
        int pn=pq.top().x;//每次选出最短路径估计最小的点
        pq.pop();
        if(vis[pn]==1)
            continue;
        vis[pn]=1;
        
        struct sss *pi=gra[pn].next;
        for(;pi!=NULL;pi=pi->next)//遍历当前节点可以直接到达的所有节点
        {
            if(vis[pi->x]==0&&dis[pn]+pi->w<dis[pi->x])
            {
                dis[pi->x]=dis[pn]+pi->w;//进行松弛操作
                pq.push((Node)
                {
                    pi->x,dis[pi->x]     //并压入栈中
                });
            }
        }
    }
    
    int ans=0;//统计最短路径和
    for(int i=1; i<=p; ++i)
        if(phc[i])
            ans+=phc[i]*dis[i];
    return ans;
}

int main()
{
    OPENFILE(butter);

    scanff();

    int i,mind=inf,ddd;
    for(i=1; i<=p; ++i)
    {
        ddd=dijkstra(i);
        if(ddd<mind)//记录最小值
            mind=ddd;
    }
    cout<<mind<<endl;

    CLOSEFILE;
    return 0;
}

 

 

 

2/SPFA

/*
ID: 15659801
LANG: C++
TASK: butter
*/
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

#define OPENFILE(s) freopen(#s".in","r",stdin);freopen(#s".out","w",stdout);//宏定义
#define CLOSEFILE fclose(stdin);fclose(stdout);
#define ll long long
#define inf 1e9+7

using namespace std;

struct sss   //邻接表存图(用邻接矩阵会TLE)
{
    int x;
    int w;
    sss *next;
} gra[801],mem[1501*2]; //静态预存节点
int cnt=-1;

int n,p,c;
int phc[801];//记录牧场有几头牛
int dis[801],vis[801];

void creat_edge(int fr,int to,int we)//邻接表存图
{
    struct sss *p=&mem[++cnt];
    p->x=to;
    p->w=we;
    p->next=gra[fr].next;
    gra[fr].next=p;
}

void scanff()
{
    cin>>n>>p>>c;
    int i,x;
    int fr,to,we;
    for(i=0; i<n; ++i)
    {
        cin>>x;
        phc[x]++;
    }
    for(i=0; i<=p; ++i) //预处理邻接表表头
    {
        gra[i].w=0;
        gra[i].next=NULL;
    }
    for(i=0; i<c; ++i)
    {
        cin>>fr>>to>>we;
        creat_edge(fr,to,we);//存图时注意是有向图还是无向图!!!!
        creat_edge(to,fr,we);
    }
}

int spfa(int s)
{
    for(int i=1; i<=p; ++i)
        dis[i]=inf;
    memset(vis,0,sizeof(vis));//vis的意义等待大神们的官方解答
    dis[s]=0;

    queue <int> pq; //定义队列
    pq.push(s);
    vis[s]=1;//表示s点在队列中

    while(!pq.empty())//SPFA核心算法
    {
        int pn=pq.front();//弹出队首元素
        pq.pop();
        vis[pn]=0;//表示队首元素已经出队
        struct sss *pi=gra[pn].next;
        for(; pi!=NULL; pi=pi->next) //遍历当前节点可以直接到达的所有节点
            if(dis[pn]+pi->w<dis[pi->x])
            {
                dis[pi->x]=dis[pn]+pi->w;//进行松弛操作
                if(vis[pi->x]==0)        //如果松弛了而且该点不在队列中,就压入队列
                {
                    pq.push(pi->x);
                    vis[pi->x]=1;//标记该元素已经入队
                }
            }
    }

    int ans=0;//统计最短路径和
    for(int i=1; i<=p; ++i)
        if(phc[i])
            ans+=phc[i]*dis[i];
    return ans;
}

int main()
{
    OPENFILE(butter1);

    scanff();

    int i,mind=inf,ddd;
    for(i=1; i<=p; ++i)
    {
        ddd=spfa(i);
        if(ddd<mind)//记录最小值
            mind=ddd;
    }
    cout<<mind<<endl;

    CLOSEFILE;
    return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值