最短路总结(迪杰斯特拉+floyed+SPFA)

今天突然发现spfa有点忘了,所以写了这篇博客,回顾回顾最短路算法,方便以后忘了可以复习复习QAQ

最短路三种算法

算法一:迪杰斯特拉算法

注意:注意:迪杰斯特拉要求图中不能有负权边

推荐博客:
http://www.cnblogs.com/biyeymyhjob/archive/2012/07/31/2615833.html

实现方法有两种,第一种就是数组标记,第二种是优先队列实现。推荐第二种,时间复杂度占优,而且可以剪枝。

第一种:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <set>
#include<malloc.h>
#include <map>
#include <vector>
#include <queue>
#define ri(n) scanf("%d",&n)
#define oi(n) printf("%d\n",n)
#define rl(n) scanf("%lld",&n)
#define ol(n) printf("%lld\n",n)
#define rep(i,l,r) for(i=l;i<=r;i++)
#define rep1(i,l,r) for(i=l;i<r;i++)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int epg=10-8;
int e[1000][1000],d[1000],vis[1000];
int n,m;
void dijiesitela(int s)
{
    for(int i=0; i<1000; i++)
    {
        d[i]=inf;
        vis[i]=0;
    }
    d[s]=0;
    while(1)
    {
        int v=-1;
        for(int i=1; i<=n; i++)
        {
            if(!vis[i]&&(v==-1||d[i]<d[v]))
            {
                v=i;
            }
        }
        if(v==-1)
            break;
        vis[v]=1;
        for(int i=1; i<=n; i++)
        {
            d[i]=min(d[i],d[v]+e[v][i]);
        }
    }
}
int main()
{
    //int n,m;//顶点数,边数
    while(scanf("%d%d",&n,&m)==2)
    {
        memset(e,inf,sizeof(e));
        for(int i=1; i<=m; i++)
        {
            int u,v,cost;
            scanf("%d%d%d",&u,&v,&cost);
            if(e[u][v]>cost)
                e[u][v]=cost;
            //e[u][v]=e[v][u]=cost;//无向图
        }
        dijiesitela(1);
        for(int i=1; i<=n; i++)
        {
            if(i==1)
                printf("%d",d[i]);
            else
                printf(" %d",d[i]);
        }
        printf("\n");
    }
    return 0;
}

第二种:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <set>
#include<malloc.h>
#include <map>
#include <vector>
#include <queue>
#define ri(n) scanf("%d",&n)
#define oi(n) printf("%d\n",n)
#define rl(n) scanf("%lld",&n)
#define ol(n) printf("%lld\n",n)
#define rep(i,l,r) for(i=l;i<=r;i++)
#define rep1(i,l,r) for(i=l;i<r;i++)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int epg=10-8;
typedef pair<int,int>p; //first是最短距离,second是点编号
struct node
{
    int to,cost;
} e;
vector<node>G[1000];
int d[1000];
void dijiesitela(int s)
{
    priority_queue<p,vector<p>,greater<p> >que;
    que.push(p(0,s));
    fill(d,d+1000,inf);
    d[s]=0;
    while(!que.empty())
    {
        p x=que.top();
        que.pop();
        int u=x.second;
        if(d[u]<x.first)
            continue;
        for(int i=0; i<G[u].size(); i++)
        {
            node y=G[u][i];
            if(d[y.to]>d[u]+y.cost)
            {
                d[y.to]=d[u]+y.cost;
                que.push(p(d[y.to],y.to));
            }
        }
    }
}
int main()
{
    int n,m;//顶点数,边数
    while(scanf("%d%d",&n,&m)==2)
    {
        node now;
        for(int i=1; i<=m; i++)
        {
            int u,v,cost;
            scanf("%d%d%d",&u,&v,&cost);
            now.to=v;
            now.cost=cost;
            G[u].push_back(now);
            now.to=u;
            //now.cost=cost;
            //G[v].push_back(now);//无向图
        }
        dijiesitela(1);//求点1到所有点的最短距离
        for(int i=1; i<=n; i++)
        {
            if(i==1)
                printf("%d",d[i]);
            else
                printf(" %d",d[i]);
        }
        printf("\n");
    }
    return 0;
}

算法二:floyed,这里不多讲,floyed算是最短路里面很常用的,但是复杂度是O(n*n *n),

typedef struct          
{        
    char vertex[VertexNum];                                //顶点表         
    int edges[VertexNum][VertexNum];                       //邻接矩阵,可看做边表         
    int n,e;                                               //图中当前的顶点数和边数         
}MGraph; 

void Floyd(MGraph g)
{
   int A[MAXV][MAXV];
   int path[MAXV][MAXV];
   int i,j,k,n=g.n;
   for(i=0;i<n;i++)
      for(j=0;j<n;j++)
      {   
             A[i][j]=g.edges[i][j];
            path[i][j]=-1;
       }
   for(k=0;k<n;k++)
   { 
        for(i=0;i<n;i++)
           for(j=0;j<n;j++)
               if(A[i][j]>(A[i][k]+A[k][j]))
               {
                     A[i][j]=A[i][k]+A[k][j];
                     path[i][j]=k;
                } 
     } 
} 

算法三:SPFA

注意:SPFA可以实现存在负权值的图的最短路求解,也可以判断图中是否存在负环

推荐博客:

(1)http://www.cnblogs.com/scau20110726/archive/2012/11/18/2776124.html

(2)https://www.61mon.com/index.php/archives/196/

SPFA算法中需要用到的主要变量

int n; //表示n个点,从1到n标号

int s,t; //s为源点,t为终点

int d[N]; //d[i]表示源点s到点i的最短路

int p[N]; //记录路径(或者说记录前驱)

queue q; //一个队列,用STL实现,当然可有手打队列,无所谓

bool vis[N]; //vis[i]=1表示点i在队列中 vis[i]=0表示不在队列中

几乎所有的最短路算法其步骤都可以分为两步

1.初始化

2.松弛操作

初始化: d数组全部赋值为INF(无穷大);p数组全部赋值为s(即源点),或者赋值为-1,表示还没有知道前驱

         然后d[s]=0;  表示源点不用求最短路径,或者说最短路就是0。将源点入队;

    (另外记住在整个算法中有顶点入队了要记得标记vis数组,有顶点出队了记得消除那个标记)

队列+松弛操作

读取队头顶点u,并将队头顶点u出队(记得消除标记);将与点u相连的所有点v进行松弛操作,如果能更新估计值(即令d[v]变小),那么就更新,另外,如果点v没有在队列中,那么要将点v入队(记得标记),如果已经在队列中了,那么就不用入队

以此循环,直到队空为止就完成了单源最短路的求解

SPFA可以处理负权边

定理: 只要最短路径存在,上述SPFA算法必定能求出最小值。

证明:

  每次将点放入队尾,都是经过松弛操作达到的。换言之,每次的优化将会有某个点v的最短路径估计值d[v]变小。所以算法的执行会使d越来越小。由于我们假定图中不存在负权回路,所以每个结点都有最短路径值。因此,算法不会无限执行下去,随着d值的逐渐变小,直到到达最短路径值时,算法结束,这时的最短路径估计值就是对应结点的最短路径值。(证毕)

期望的时间复杂度O(ke), 其中k为所有顶点进队的平均次数,可以证明k一般小于等于2。

**判断有无负环:

  如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)**

SPFA的两种写法,bfs和dfs,bfs判别负环不稳定,相当于限深度搜索,但是设置得好的话还是没问题的,dfs的话判断负环很快

一:bfs写法:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <set>
#include<malloc.h>
#include <map>
#include <vector>
#include <stack>
#include <queue>
#define ri(n) scanf("%d",&n)
#define oi(n) printf("%d\n",n)
#define rl(n) scanf("%lld",&n)
#define ol(n) printf("%lld\n",n)
#define rep(i,l,r) for(i=l;i<=r;i++)
#define rep1(i,l,r) for(i=l;i<r;i++)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int epg=10-8;
const int maxn=1000;
int n,m;//顶点数,边数
int e[maxn][maxn];
int vis[maxn],d[maxn],c[maxn],path[maxn];
bool SPFA(int s)
{
    for(int i=0; i<maxn; i++)
        path[i]=s;
    queue<int>p;
    p.push(s);
    vis[s]=1;
    c[s]++;
    d[s]=0;
    while(!p.empty())
    {
        int u=p.front();
        p.pop();
        vis[u]=0;
        for(int v=1; v<=n; v++)
        {
            if(e[u][v]!=inf)//如果u点和i点直接相邻,就是u,i点之间有边
            {
                if(d[v]>d[u]+e[u][v])
                {
                    d[v]=d[u]+e[u][v];
                    path[v]=u;

                    if(!vis[v])
                    {
                        p.push(v);
                        c[v]++;
                        if(c[v]>=n)
                            return false;
                        vis[v]=1;
                    }
                }
            }
        }
    }
    return true;
}
void print(int s)
{
    for(int i=1;i<=n;i++)
    {
        if(i!=s)
        {
            int u=i;
            stack<int>ok;
            cout<<s<<"点到"<<i<<"点的最短路="<<d[i]<<endl;
            while(u!=s)
            {
                ok.push(u);
                u=path[u];
            }
            cout<<s;
            while(!ok.empty())
            {
                cout<<"--"<<ok.top();
                ok.pop();
            }
            cout<<endl;

        }
        else
            cout<<d[i]<<endl;
    }
}
int main()
{
    while(scanf("%d%d",&n,&m)==2)
    {
        memset(e,inf,sizeof(e));
        memset(vis,0,sizeof(vis));
        memset(d,inf,sizeof(d));
        memset(c,0,sizeof(c));
        for(int i=1; i<=m; i++)
        {
            int u,v,cost;
            scanf("%d%d%d",&u,&v,&cost);
            if(e[u][v]>cost)
                e[u][v]=cost;
            //e[u][v]=e[v][u]=cost;//无向图
        }
        if(!SPFA(1))//求点1到所有点的最短路
         printf("有负环");
        else
            print(1);
    }
    return 0;
}

二:dfs写法

int spfa_dfs(int u)
{
    vis[u]=1;
    for(int k=f[u]; k!=0; k=e[k].next)
    {
        int v=e[k].v,w=e[k].w;
        if( d[u]+w < d[v] )
        {
            d[v]=d[u]+w;
            if(!vis[v])
            {
                if(spfa_dfs(v))
                    return 1;
            }
            else
                return 1;
        }
    }
    vis[u]=0;
    return 0;
}

最短路练习题目:https://vjudge.net/contest/66569#problem

题目对应代码:

方法一:优先队列(迪杰斯特拉)
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <set>
#include <map>
#include <vector>
#include <queue>
#define ri(n) scanf("%d",&n)
#define oi(n) printf("%d\n",n)
#define rl(n) scanf("%lld",&n)
#define ol(n) printf("%lld\n",n)
#define rep(i,l,r) for(i=l;i<=r;i++)
#define rep1(i,l,r) for(i=l;i<r;i++)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int epg=10-8;
typedef pair<int,int>p; //first是最短距离,second是点编号
struct node
{
    int to,cost;
} e;
vector<node>G[2000+10];
int d[2000+10];
void dijiesitela(int s)
{
    priority_queue<p,vector<p>,greater<p> >que;
    que.push(p(0,s));
    fill(d,d+2010,inf);
    d[s]=0;
    while(!que.empty())
    {
        p x=que.top();
        que.pop();
        int u=x.second;
        if(d[u]<x.first)
            continue;
        for(int i=0; i<G[u].size(); i++)
        {
            node y=G[u][i];
            if(d[y.to]>d[u]+y.cost)
            {
                d[y.to]=d[u]+y.cost;
                que.push(p(d[y.to],y.to));
            }
        }
    }
}
int main()
{
    int n,m;//顶点数,边数
    while(scanf("%d%d",&m,&n)==2)
    {
        node now;
        for(int i=1; i<=m; i++)
        {
            int u,v,cost;
            scanf("%d%d%d",&u,&v,&cost);
            now.to=v;
            now.cost=cost;
            G[u].push_back(now);
            now.to=u;
            now.cost=cost;
            G[v].push_back(now);
        }
        dijiesitela(1);
//        for(int i=1; i<=n; i++)
//        {
//            if(i==1)
//                printf("%d",d[i]);
//            else
//                printf(" %d",d[i]);
//        }
//        printf("\n");
    printf("%d\n",d[n]);
    }
    return 0;
}



方法二:数组(迪杰斯特拉)
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <set>
#include <map>
#include <vector>
#include <queue>
#define ri(n) scanf("%d",&n)
#define oi(n) printf("%d\n",n)
#define rl(n) scanf("%lld",&n)
#define ol(n) printf("%lld\n",n)
#define rep(i,l,r) for(i=l;i<=r;i++)
#define rep1(i,l,r) for(i=l;i<r;i++)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int epg=10-8;
int e[2000+10][2000+10],d[2000+10],vis[2000+10];
int n,m;
void dijiesitela(int s)
{
    for(int i=0; i<2000; i++)
    {
        d[i]=inf;
        vis[i]=0;
    }
    d[s]=0;
    while(1)
    {
        int v=-1;
        for(int i=1; i<=n; i++)
        {
            if(!vis[i]&&(v==-1||d[i]<d[v]))
            {
                v=i;
            }
        }
        if(v==-1)
            break;
        vis[v]=1;
        for(int i=1;i<=n;i++)
        {
            d[i]=min(d[i],d[v]+e[v][i]);
        }
    }
}
int main()
{
    //int n,m;//顶点数,边数
    while(scanf("%d%d",&m,&n)==2)
    {
        memset(e,inf,sizeof(e));
//        for(int i=1;i<=n;i++)
//            for(int j=1;j<=n;j++)
//        {
//            if(i==j)
//                e[i][j]=0;
//            else
//                e[i][j]=inf;
//        }
        for(int i=1; i<=m; i++)
        {
            int u,v,cost;
            scanf("%d%d%d",&u,&v,&cost);
            if(e[u][v]>cost)
                 e[u][v]=e[v][u]=cost;
        }
        dijiesitela(1);
//        for(int i=1; i<=n; i++)
//        {
//            if(i==1)
//                printf("%d",d[i]);
//            else
//                printf(" %d",d[i]);
//        }
        printf("%d\n",d[n]);
    }
    return 0;
}


方法三:SPFA
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <set>
#include<malloc.h>
#include <map>
#include <vector>
#include <stack>
#include <queue>
#define ri(n) scanf("%d",&n)
#define oi(n) printf("%d\n",n)
#define rl(n) scanf("%lld",&n)
#define ol(n) printf("%lld\n",n)
#define rep(i,l,r) for(i=l;i<=r;i++)
#define rep1(i,l,r) for(i=l;i<r;i++)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int epg=10-8;
const int maxn=2000+10;
int n,m;//顶点数,边数
int e[maxn][maxn];
int vis[maxn],d[maxn],c[maxn],path[maxn];
bool SPFA(int s)
{
    for(int i=0; i<maxn; i++)
        path[i]=s;
    queue<int>p;
    p.push(s);
    vis[s]=1;
    c[s]++;
    d[s]=0;
    while(!p.empty())
    {
        int u=p.front();
        p.pop();
        vis[u]=0;
        for(int v=1; v<=n; v++)
        {
            if(e[u][v]!=inf)//如果u点和v点直接相邻,就是u,v点之间有边
            {
                if(d[v]>d[u]+e[u][v])
                {
                    d[v]=d[u]+e[u][v];
                    path[v]=u;

                    if(!vis[v])
                    {
                        p.push(v);
                        c[v]++;
                        if(c[v]>=n)
                            return false;
                        vis[v]=1;
                    }
                }
            }
        }
    }
    return true;
}
void print(int s)
{
    for(int i=1;i<=n;i++)
    {
        if(i!=s)
        {
            int u=i;
            stack<int>ok;
            cout<<s<<"点到"<<i<<"点的最短路="<<d[i]<<endl;
            while(u!=s)
            {
                ok.push(u);
                u=path[u];
            }
            cout<<s;
            while(!ok.empty())
            {
                cout<<"--"<<ok.top();
                ok.pop();
            }
            cout<<endl;

        }
        else
            cout<<d[i]<<endl;
    }
}

int main()
{
    while(scanf("%d%d",&m,&n)==2)
    {
        memset(e,inf,sizeof(e));
        memset(vis,0,sizeof(vis));
        memset(d,inf,sizeof(d));
        memset(c,0,sizeof(c));
        for(int i=1; i<=m; i++)
        {
            int u,v,cost;
            scanf("%d%d%d",&u,&v,&cost);
            if(e[u][v]>cost)
                //e[u][v]=cost;
            e[u][v]=e[v][u]=cost;//无向图
        }
        SPFA(1);
        //if(!SPFA(1))//求点1到所有点的最短路
         //printf("有负环");
        //else
            //print(1);
        printf("%d\n",d[n]);
    }
    return 0;
}

推荐一个题目:https://vjudge.net/problem/HDU-6005

题解:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <set>
#include<malloc.h>
#include <map>
#include <vector>
#include <queue>
#define ri(n) scanf("%d",&n)
#define oi(n) printf("%d\n",n)
#define rl(n) scanf("%lld",&n)
#define ol(n) printf("%lld\n",n)
#define rep(i,l,r) for(i=l;i<=r;i++)
#define rep1(i,l,r) for(i=l;i<r;i++)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int epg=10-8;
int cnt,ans;
typedef pair<int,int>p;
map<p,int>m;
vector<p>G[10000+10];
int vis[10000+10],d[10000+10];
struct node
{
    int u,v,cost;
}e[10000+10];
void init()
{
    cnt=0;
    ans=inf;
    for(int i=0;i<10000+10;i++)
    {
        G[i].clear();
    }
    //memset(vis,0,sizeof(vis));
    m.clear();
}
void dij(int s,int t,int cost)
{
    priority_queue<p,vector<p>,greater<p> >q;
    for(int i=0;i<10000+10;i++)
    {
        d[i]=inf;
        vis[i]=0;
    }
    d[s]=0;
    q.push(make_pair(0,s));
    while(!q.empty())
    {
        p x=q.top();
        q.pop();
        int u=x.second,dis=x.first;
        if(dis+cost>=ans)
            break;
        if(vis[u])
            continue;
        vis[u]=1;
        for(int i=0;i<G[u].size();i++)
        {
            int v=G[u][i].first,id=G[u][i].second;
            int val=e[id].cost;
            if(d[v]>d[u]+val)
            {
                d[v]=d[u]+val;
                q.push(make_pair(d[v],v));
            }
        }
    }
    ans=min(ans,d[t]+cost);
}
int main()
{
    int t;
    scanf("%d",&t);
    int kases=0;
    while(t--)
    {
        int n;
        scanf("%d",&n);
        init();
        for(int i=1;i<=n;i++)
        {
            int x1,x2,y1,y2,val;
            scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&val);
            p p1=make_pair(x1,y1);
            p p2=make_pair(x2,y2);
            if(!m[p1]) m[p1]=++cnt;
            if(!m[p2]) m[p2]=++cnt;
            int u=m[p1],v=m[p2];
            G[u].push_back(make_pair(v,i));
            G[v].push_back(make_pair(u,i));
            e[i].u=u;e[i].v=v;e[i].cost=val;
        }
        for(int i=1;i<=n;i++)
        {
            int u=e[i].u,v=e[i].v,cost=e[i].cost;
            e[i].cost=inf;
            dij(u,v,cost);
            e[i].cost=cost;
        }
        if(ans==inf)
            ans=0;
        printf("Case #%d: %d\n",++kases,ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值