最短路复习

Posted tangzhiyang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最短路复习相关的知识,希望对你有一定的参考价值。

其实这篇文章曾经在初赛复习里,只是觉得把它拿出来以后可能找的更方便一些。

- Dijkstra:贪心策略,每次取与源点距离最近的点。一个点不能重复入堆,不适用于负权边(或负环)

  1. 朴素版
#include<bits/stdc++.h>
using namespace std;
#define inf 1e8
int mark[10005],dis[10005];
struct node{int to,v;};
vector<node>edge[10005];
int main(){
    int n,m,s,t,x,y,z;
    scanf("%d%d%d%d",&n,&m,&s,&t);
    memset(dis,0,sizeof(dis));dis[s]=0;
    for(int i=1;i<=m;i++)scanf("%d%d%d",&x,&y,&z),edge[x].push_back(node<%y,z%>);
    for(int j=1;j<=n;j++){
        int len=inf*2,p;
        for(int i=1;i<=n;i++)
            if(!mark[i]&&dis[i]<len)len=dis[i],p=i;
        mark[p]=1;
        for(int i=0;i<edge[p].size();i++){
            int b=edge[p][i].to;
            int c=edge[p][i].v;
            if(!mark[b]&&dis[b]>dis[p]+c)
                dis[b]=dis[p]+c;
        }
    }
    if(dis[t]==inf)cout<<-1;
    else cout<<dis[t];
}

时间复杂度 (O(n^2))

2.堆优化:

#include<bits/stdc++.h>
using namespace std;
#define inf 1e8
struct node{int to,v;};
bool operator<(node a,node b){return a.v>b.v;}
priority_queue<node>q;
vector <node> edge[10005];
int mark[10005],dis[10005];
int main(){
    int n,m,s,t,a,b,c;
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(int i=1;i<=n;i++)dis[i]=inf;
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&a,&b,&c);
        edge[a].push_back(node<%b,c%>);
    }
    dis[s]=0;
    q.push((node){s,0});
    while(!q.empty()){
        node tmp=q.top();q.pop();
        int k=tmp.to;
        if(mark[k])continue;
        mark[k]=1;
        for(int i=0;i<edge[k].size();i++){
            int y=edge[k][i].to;
            int v=edge[k][i].v;
            if(dis[y]>dis[k]+v){
                dis[y]=dis[k]+v;
                q.push((node){y,dis[y]});
            }
        }
    }
    if(dis[t]==inf)dis[t]=-1;
    printf("%d",dis[t]);
}

时间复杂度:(O((m+n)logn))

以前打堆优化好像有很多都在mark的地方打错了,但数据比较水。

提供一组hack:

4 4 1 4
1 2 1
1 3 2
2 4 6
3 4 2

输出:4

- bellmanford:时间复杂度 (O(n * m)),适用于负权边,可以判负环。

#include<bits/stdc++.h>
using namespace std;
struct node{int from,to,v;}edge[5005];
int n,m,dis[2505];
namespace bellmanford{
    void solve(){
        for(int i=1;i<=m;i++)
            scanf("%d%d%d",&edge[i].from,&edge[i].to,&edge[i].v);
        memset(dis,127,sizeof(dis));
        int inf=dis[0];
        dis[1]=0;
        for(int i=1;i<=n;i++){
            bool flag=0;
            for(int j=1;j<=m;j++){
                int a=edge[j].from;
                int b=edge[j].to;
                int c=edge[j].v;
                if(dis[a]==inf)continue;
                if(dis[b]>dis[a]+c){
                    if(i<n)dis[b]=dis[a]+c;
                    flag=1;
                }
            }
            if(!flag)break;
        }
        if(dis[n]==inf)puts("-1");
        else cout<<dis[n];
    }
}
int main(){
    scanf("%d%d",&n,&m);
    bellmanford::solve();
}

如果想要判负环的话,就可以把最外面那层循环变成 (2 * n),如果 (i > n)后还有被更新的话就说明存在负环。

- SPFA: 关于SPFA,他已经死了,一个点可以重复入队,是bellmanford的队列优化,复杂度比较神奇,可能会被卡成 (n * m)

#include<bits/stdc++.h>
using namespace std;
#define inf 1e8
struct node{int to,v;};
vector <node> edge[10005];
int dis[10005],isin[10005],num[10005];
queue<int>q;
int main(){
    int n,m,s,t,a,b,c;
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(int i=1;i<=n;i++)dis[i]=inf;
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&a,&b,&c);
        edge[a].push_back(node<%b,c%>);
    }
    dis[s]=0;
    q.push(s);isin[s]=1;num[s]=1;
    while(!q.empty()){
        int nw=q.front();q.pop();isin[nw]=0;
        for(int i=0;i<edge[nw].size();i++){
            int y=edge[nw][i].to,v=edge[nw][i].v;
            if(dis[y]>dis[nw]+v){
                dis[y]=dis[nw]+v;
                if(isin[y])continue;
                isin[y]=1;
                q.push(y);
                num[y]++;
                if(num[y]>n){puts("有负环");return 0;}
            }
        }
    }
    if(dis[t]==inf)dis[t]=-1;
    printf("%d",dis[t]);
}

记得pop的时候要标记一下它已经不在队列里了。

- Floyd:最暴力的最短路,时间复杂度(O(n^3)),可以求出任意两个点之间的最短路,也是最短的,代码最好记的。

#include<bits/stdc++.h>
using namespace std;
int n,m;
int dis[105][105];
void Floyd(){
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(dis[i][j]>dis[i][k]+dis[k][j])
                    dis[i][j]=dis[i][k]+dis[k][j];
}
int main(){
    int a,b,c;
    scanf("%d%d",&n,&m);
    memset(dis,0x3f,sizeof(dis));
    int inf=dis[0][0];
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&a,&b,&c);
        dis[a][b]=min(dis[a][b],c);
    }
    for(int i=1;i<=n;i++)dis[i][i]=0;
    Floyd();
    if(dis[1][n]==inf)dis[1][n]=-1;
    printf("%d",dis[1][n]);
}

三个循环中,k的含义是用k个节点联通两个点。

以上是关于最短路复习的主要内容,如果未能解决你的问题,请参考以下文章

NOIP复习最短路总结

最短路问题的思路拓展

3-1复习最短路径算法,3-2学习二叉数结构

图论复习...

图论复习

最短路径图示标数法(复习)