最短路算法
最短路算法
今天,我们来总结图的最短路算法,它的基础算法如下:
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 }
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条跑道。
输入输出样例
2 6 6 4 10 11 4 6 4 4 8 8 4 9 6 6 8 2 6 9 3 8 9
#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...

浙公网安备 33010602011771号