最短路算法

最短路算法

  今天,我们来总结图的最短路算法,它的基础算法如下:

part1:单源最短路

一、dijkstra算法。

1核心思想:贪心算法=>每次找最短的边,标记该点并对其余未标记点松弛。

2模板:

void dijkstra(){
    for(int j=1;j<=n;j++){
        int Min=inf,p;//较大值INF 
        for(int i=1;i<=n;i++)
            if(!vis[i]&&Min>dis[i]) Max=dis[i],p=i;
        vis[p]=1;
        for(int i=1;i<=n;i++)dis[i]=min(dis[i],dis[p]+way(p->i));
    }//ans为dis[k] 
}

时间复杂度:o(n2

  3优化:找最短边的时候可采用大根堆(优先队列实现) 时间复杂度O(m+nlogn),代码如下:

int n,m,s,u,v,w,dis[N];
bool vis[N];
vector <pair<int,int> > g[N];
pair <int,int> t;
priority_queue <pair<int,int> > q;
int main(){
    dis[s]=0;
    q.push(make_pair(0,s));
    while(!q.empty()){
        int x=q.top().second;//1为边,2为点 
        q.pop() ;
        if(vis[x]) continue;
        vis[x]=1;
        for(int i=0;i<g[x].size() ;i++){
            t=g[x][i];
            int dd=t.first ,y=t.second ;
            if(vis[y]) continue;
            if(dis[y]>dd+dis[x]){//松弛 
                dis[y]=dd+dis[x];
                q.push(make_pair(-dis[y],y)); 
            }
        }
    }
}

 

注意:为适应优先队列(小根堆),pair第一维是边(带负号,使之弹出边最小的),第二维是点。

PS:dijistra 不适用于负边权(贪心失效)

二、bellman ford算法

1核心思想:动规f[i][j][k]=> i->j 经历k步的min;

2转移方程:f[i][j]=min(f[i][j],f[i][k]+way(k->j) (k是某点,第三维省略)

3原理:一个图的最短路最多更新n-1次;

4空间复杂度o(mn);

5优势:可适用于负边权,可判负环(如果第n+1次仍存在松弛,则存在负环)

代码什么的......这个不重要(反正也几乎不用),我们看优化版的SPFA的吧

三、SPFA算法——bellman的优化

1核心思想:避免重复无效计算=>基于队列的优化,能松弛的点才入队。

2时间复杂度o(km)(PS:被卡时退化成o(nm));

3优势:代码较短,可判负环,可用于负边权。

代码如下:

 

void SPFA(){
    memset(dis,127,sizeof(dis));//初始化为max 
    dis[s]=0,vis[s]=1;q.push(1);//入队打标记 
    while(!q.empty()){
        int u=q.front();vis[u]=0;q.pop();//出队去标记 
        for(int i=0;i<g[u].size() ;i++){//vector存的邻接表 
            int v=g[u][i].second,w=g[u][i].first;
            if(dis[v]>dis[u]+w){
                dis[v]=dis[u]+w,t[v]++;//松弛并判断次数 
                if(!vis[v]) vis[v]=1,q.push(v) ;//入队打标记
                if(t[v]>n) {cout<<NO;return;}//存在负环 
            }
        }
    }
}

 

part2:多源最短路

四、Floyd算法

1核心思想:动规,f[i][j][k]=>  i->j只经过点1~k的min

2转移方程:f[i][j]=min(f[i][j],f[i][k]+f[k][j])(第三维省略)

3时间复杂度o(n3

核心代码如下:(极简)

 

for(int k=1;i<=n;k++)//中转站 
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            f[i][j]=min(f[i][j],f[i][k]+f[k][j]);

 

PS:k(中转站)作为第三维一定是放在嵌套的最外层

 

PS:Floyd在稠密图中表现优秀,稀疏图中可考虑n次dijkstra

part3:例题

eg1:奶牛的比赛

FJ的N(1 <= N <= 100)头奶牛们最近参加了场程序设计竞赛:)。在赛场上,奶牛们按1..N依次编号。每头奶牛的编程能力不尽相同,并且没有哪两头奶牛的水平不相上下,也就是说,奶牛们的编程能力有明确的排名。 整个比赛被分成了若干轮,每一轮是两头指定编号的奶牛的对决。如果编号为A的奶牛的编程能力强于编号为B的奶牛(1 <= A <= N; 1 <= B <= N; A != B) ,那么她们的对决中,编号为A的奶牛总是能胜出。 FJ想知道奶牛们编程能力的具体排名,于是他找来了奶牛们所有 M(1 <= M <= 4,500)轮比赛的结果,希望你能根据这些信息,推断出尽可能多的奶牛的编程能力排名。比赛结果保证不会自相矛盾。

输入格式

第1行: 2个用空格隔开的整数:N 和 M

第2..M+1行: 每行为2个用空格隔开的整数A、B,描述了参加某一轮比赛的奶 牛的编号,以及结果(编号为A,即为每行的第一个数的奶牛为 胜者)

输出格式

第1行: 输出1个整数,表示排名可以确定的奶牛的数目

输入输出样例

输入 #    输出 #
5 5      2
4 3
4 2
3 2
1 2
2 5

说明/提示

输出说明:

编号为2的奶牛输给了编号为1、3、4的奶牛,也就是说她的水平比这3头奶牛都差。而编号为5的奶牛又输在了她的手下,也就是说,她的水平比编号为5的

奶牛强一些。于是,编号为2的奶牛的排名必然为第4,编号为5的奶牛的水平必然最差。其他3头奶牛的排名仍无法确定。(sourse:洛谷 P2419)

  分析:这是一道Floyd的变式,什么时候能确定奶牛的具体排名呢?当且仅当其余奶牛与它能比较时成立,因此,我们可以用

bool f[i][j] 处理关系(1表示i强于j),对输入中任意一组关系a,b,f[a][b]=1,f[b][a]=0,用Floyd的n次循环来处理,当f[i][k]=1&&f[k][j]==1时有f[i][j]=1

code:

 1 #include <bits/stdc++.h>
 2 const int N=101; 
 3 using namespace std;
 4 bool f[N][N];int n,m,ans=0;
 5 void judge(int x){
 6     int sum=0;
 7     for(int i=1;i<=n;i++) if(f[i][x]||f[x][i]) sum++;
 8     if(sum==n-1) ans++;//精华,排名具体成立条件 
 9 }
10 int main(){
11     cin>>n>>m;
12     for(int i=1;i<=m;i++){
13         int a,b;
14         cin>>a>>b;
15         f[a][b]=1;
16     }
17     for(int k=1;k<=n;k++)
18         for(int i=1;i<=n;i++)
19             for(int j=1;j<=n;j++)
20                 f[i][j]|=f[i][k]&f[k][j];//精华,逻辑运算 
21     for(int i=1;i<=n;i++) judge(i);
22     cout<<ans;
23     return 0;
24 } 

 

  eg3:奶牛接力(source:洛谷 P2886

  FJ 的N(2 <= N <= 1,000,000)头奶牛选择了接力跑作为她们的日常锻炼项目。至于进行接力跑的地点,自然是在牧场中现有的T(2 <= T <= 100)条跑道上。农场上的跑道有一些交汇点,每条跑道都连结了两个不同的交汇点L1i和L2i(1 <=L1i <=1,000; 1 <= L2i <= 1,000)。每个交汇点都是至少两条跑道的端点。奶牛们知道每条跑道的长度lengthi(1 <= l engthi <= 1,000),以及每条跑道连结的交汇点的编号。并且,没有哪两个交汇点由两条不同的跑道直接相连。你可以认为这些交汇点和跑道构成了一张图。为了完成一场接力 跑,所有N 头奶牛在跑步开始之前都要站在某个交汇点上(有些交汇点上可能站着不只1头奶牛)。当然,她们的站位要保证她们能够将接力棒顺次传递,并且最后持棒的奶牛要停 在预设的终点。你的任务是,写一个程序,计算在接力跑的起点(S)和终点(E)确定的情况下,奶牛们跑步路径可能的最小总长度。显然,这条路径必须恰好经过N条跑道。

  输入输出样例

 输入 #1  输出 #1
2 6 6 4 10
11 4 6
4 4 8
8 4 9
6 6 8
2 6 9
3 8 9
  大意:给出一张无向连通图,求S到E经过k条边的最短路。
  简析:这道题又是Floyd的变式(怎么又是奶牛),我们可以用f[i][j][k](其中第三维可省略)来表示从i到j,经过k条边的min
  当k=0时只有f[i][i]=0;
  当k=1时只有f[i][j]=way(i->j)更新
  当k=2
#include <bits/stdc++.h>
using namespace std;
int n,t,s,e,top=0,re[1001];
struct juzhen{
    long long v[101][101];
}g,ans,c;//单开结构体,方便传数 
juzhen floyed(juzhen a,juzhen b){
    memset(c.v ,0x3f,sizeof(c.v ));
    for(int i=1;i<=top;i++){
        for(int j=1;j<=top;j++){
            for(int k=1;k<=top;k++){
                c.v [i][j]=min(c.v [i][j],a.v [i][k]+b.v [k][j]);
            }
        }
    }
    return c;
}
void cheng(int n){//矩阵乘法 
    while(n){//快速幂 
        if(n&1) ans=floyed(ans,g);
        g=floyed(g,g);
        n>>=1;
    }
}
int main(){
    scanf("%d%d%d%d",&n,&t,&s,&e);

    for(int i=1;i<=t;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        if(re[b]==0) re[b]=++top;//reflection处理使之映射在连续的点上
        if(re[c]==0) re[c]=++top;//即离散化 
        b=re[b],c=re[c];
        if(g.v[b][c]>a)g.v[c][b]=g.v[b][c]=a;//存图 
    }
    s=re[s],e=re[e];//映射 
    memset(g.v ,0x3f,sizeof(g.v));
    memset(ans.v ,0x3f,sizeof(ans.v ));
    for(int i=1;i<=top;i++) ans.v [i][i]=0;//开始时(k=0)只有到自己的距离为零
    cheng(n);
    printf("%lld",ans.v[s][e]);
    return 0;
}

 To be continued...

 

 

 

posted @ 2020-01-20 16:30  窝火居高临下  阅读(161)  评论(0)    收藏  举报