第三关 ——图论:最短路
Posted wybxz
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第三关 ——图论:最短路相关的知识,希望对你有一定的参考价值。
14:52:09 今天天气好晴朗,处处好风光。
阳光犹如希望,我即将要回去打游戏的希望,希望那么那么大,那么那么刺眼!!!
好的,接下来为您播报考试后续情况:不算太差,还看得过去,至少,应该能活着把年过过去。接下来,就介绍一下在父母看到成绩后想要打你时如何通过最短路逃跑
目录
-
Dijkstra算法
-
Bellman-Ford算法
-
SPFA算法
-
Floyd算法
首先,要来简单论述一下“最短路”这个问题。要求最短路,无论什么方法,都要先存图。
百“路”图为先
那么,图分为两种,有向图与无向图。无论是有向图还是无向图,存图方式都是一样的。分为两种:
- 邻接矩阵(空间复杂度:O(n2))
可以说,矩阵存图是非常非常的简单易打了。话不多说,放代码
int n,m; cin>>n>>m; memset(e,127,sizeof(e));//将矩阵中所有边初始化为无穷大 for(int i=1; i<=n; i++) e[i][i]=0; for(int i=1; i<=m; i++) { int x,y,z; cin>>x>>y>>z;//x点与y点有一条边,边的权值为z e[x][y]=z; e[y][x]=z; }
- 邻接表(空间复杂度:O(n+m))
邻接表可以用数组模拟链表指针:
void add(int x,int y,int z)//x点与y点有一条边,边的权值为z { ver[++tot]=y,edge[tot]=z; next[tot]=head[x],head[x]=tot; } for(int i=head[x];i;i=next[i]) { int y=ver[i],z=edge[i]; }//于主函数中
也可以用vector存储:
vector<int>e[1000];//vector代替邻接表 for(int i=1;i<=m;i++) { int x,y,z; scanf("%d%d",&x,&y);//x,y两点间有一条边 e[x].push_back(y);//加入x的vector }
好的,接下来开始讲我们的单源最短路径的几种算法(单源最短路径:源点到每个点的距离)
-
Dijkstra算法(基于贪心思想,适用于没有负权的图)
算法流程
- 初始化dis[1]=0,其余dis数值为正无穷大
- 找到未被标记的最小节点x,并标记
- 扫描x的所有出边,若dis[y]>dis[x]+z(边权),则用dis[x]+z更新dis[y]的值
- 直到节点被标记完为止
dis[x]代表源点到x的最短路径。
它的时间复杂度为(O(logn))
单源最短路问题
题目描述
输入一个无向网络,输出其中2个顶点之间的最短路径长度
输入
输入文件第一行为n和m,表示有n个顶点和m条带权边,其中顶点编号是从1到n,接下来有m行,每行三个整数,分别表示两个顶点编号和对应边的权值,再接下来有一行,两个整数表示要求的最短路径的两个顶点编号
输出
输出文件就一行,即两个顶点间的最短路径长度(权值和)
输入样例
4 5 1 2 2 1 3 1 2 3 2 2 4 1 3 4 6 1 4
输出样例
3
这里需要用vis数组存储是否标记。
#include<bits/stdc++.h> using namespace std; int e[1000][1000],dis[1000],vis[1000]; int main() { int n,m; cin>>n>>m; memset(e,127,sizeof(e)); for(int i=1; i<=n; i++) e[i][i]=0; for(int i=1; i<=m; i++) { int x,y,z; cin>>x>>y>>z; e[x][y]=z; e[y][x]=z; } int s,b; cin>>s>>b; for(int i=1; i<=n; i++) { dis[i]=e[s][i]; } vis[s]=1; for(int i=1; i<=n; i++) { int mmin=99999,k; for(int j=1; j<=n; j++) { if(vis[j]!=1) { if(dis[j]<mmin) { mmin=dis[j]; k=j; } } } vis[k]=1; for(int j=1; j<=n; j++) { dis[j]=min(dis[j],dis[k]+e[k][j]); } } cout<<dis[b]; return 0; }
Dijkstra堆优化(时间复杂度(O(n2)))
堆排序
#include<iostream> #include<queue> using namespace std; priority_queue<int, vector<int>, greater<int> >q; int main(){ int n,x; cin>>n; for(int i=1;i<=n;i++){ cin>>x; q.push(x); } while(!q.empty()){ cout<<q.top()<<" "; q.pop(); } return 0; }
https://www.luogu.com.cn/problem/P4779
所以就有了堆优化的Dijkstra
堆中每个元素存两个值 结点编号xx和入堆是该点被更新成的距离disdis
disdis为第一关键字(即按dis的小根堆) 主要思想不变 每次找距离最小点是直接取出堆顶并删除堆顶
更新最短路的时候 将更新后的节点入堆
注意一个点可能会被更新多次而入堆多次 但是只有最后一次入堆才是正确的dis 同时也一定是在所有入堆操作结束后才会出堆 所以直接开个数组判断有没有出堆过就好了 当然也可以根据堆中元素的距离大小和点的最短路大小直接判断
#include<bits/stdc++.h> #define M(x,y) make_pair(x,y) using namespace std; int fr[100010],to[200010],nex[200010],v[200010],tl,d[100010]; bool b[100010]; void add(int x,int y,int w){ to[++tl]=y; v[tl]=w; nex[tl]=fr[x]; fr[x]=tl; } priority_queue< pair<int,int> > q; int main(){ int n,m,x,y,z,s; scanf("%d%d%d",&n,&m,&s); for(int i=1;i<=m;i++){ scanf("%d%d%d",&x,&y,&z); add(x,y,z); } for(int i=1;i<=n;i++) d[i]=1e10; d[s]=0; q.push(M(0,s)); while(!q.empty()){ int x=q.top().second; q.pop(); if(b[x]) continue; b[x]=1; for(int i=fr[x];i;i=nex[i]){ int y=to[i],l=v[i]; if(d[y]>d[x]+l){ d[y]=d[x]+l; q.push(M(-d[y],y)); } } } for(int i=1;i<=n;i++) printf("%d ",d[i]); return 0; }
-
Bellman-Ford算法和SPFA算法
Bellman-Ford算法
算法流程:
- 扫描所有边(x,y,z),若dis[y]>dis[x]+z(边权),则用dis[x]+z更新dis[y]的值
- 重复直到没有更新
- 时间复杂度(O(nm))
SPFA算法
算法流程:
- 建立一个队列,最初队列只含起点1
- 取队头结点x,扫描它的所有出边,若dis[y]>dis[x]+z(边权),则用dis[x]+z更新dis[y]的值
- 若y不在队列中的话,将y入队
- 重复直到队列为空
- 时间复杂度(O(km))
- 可解决负权,也可判断有无负环
可惜的是,该算法已死。
以1为起点,在原图上使用SPFA算法,用数组d[x]表示在原图中从节点1到节点x的所有路径中,能够经过的权值最小的节点的权值
以n为起点,在反图上使用SPFA算法,用数组f[x]表示在原图中从节点x到节点n的所有路径中(反图中是n到x),能够经过的权值最大的节点的权值
最后,枚举每个节点x,用f[x]-d[x]更新答案
#include<bits/stdc++.h> using namespace std; const int N=100002; int a[N],b[N],c[N]; vector<int> g[N]; vector<int> s[N]; queue<int> q; int main() { int n,m; cin>>n>>m; for(int i=1;i<=n;++i) cin>>a[i]; for(int i=1;i<=m;i++) { int x,y,z; cin>>x>>y>>z; g[x].push_back(y); s[y].push_back(x); if(z==2) { g[y].push_back(x); s[x].push_back(y); } } q.push(1); memset(c,60,sizeof(c)); c[1]=9999999; while(!q.empty()) { int t=q.front(); q.pop(); c[t]=min(c[t],a[t]); for(int i=0;i<g[t].size();i++) if(c[t]<c[g[t][i]]) c[g[t][i]]=c[t],q.push(g[t][i]); } q.push(n); while(!q.empty()) { int t=q.front(); q.pop(); b[t]=max(b[t],a[t]); for(int i=0;i<s[t].size();i++) if(b[t]>b[s[t][i]]) b[s[t][i]]=b[t],q.push(s[t][i]); } int ans=0; for(int i=1;i<=n;i++) ans=max(ans,b[i]-c[i]); cout<<ans; return 0; }
20:12:48 所以暂时将你眼睛闭了起来 ——伍佰
多源最短路径(任意两点间最短路径)
-
Floyd算法
状态转移方程:d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
可用邻接矩阵存图
每队顶点之间的最短路
题目描述
输入一个有向图的邻接矩阵格式,输出每对顶点间的最短路径长度
输入
输入第一行为n,表示下面是个n n的矩阵,接下来就是n n的矩阵形式,每个元素值都是整型,如果不能直接到,则是-1
输出
输出也是一个n * n的矩阵形式,每个元素的值为两个顶点间的最短路径值,如果到不了,则输出-1,最后一行的换行也输出
输入样例
3
0 1 3
2 0 5
3 2 0
输出样例
0 1 3 2 0 5 3 2 0
这是一道模板题,所以直接看代码。
#include<bits/stdc++.h> using namespace std; int a[60][60]; int x,y,n; int main() { cin>>n; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { cin>>a[i][j]; if(a[i][j]==-1) a[i][j]=9999999; } for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) a[i][j]=min(a[i][j],a[i][k]+a[k][j]); for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { if(a[i][j]==9999999) a[i][j]=-1; cout<<a[i][j]<<" "; } cout<<endl; } return 0; }
文化之旅
因为Floyd是插点找最短路,所以每次状态都会被记录,插的点的文化就会被记录在这条路里,比如若在i,j中插入k时没有矛盾且满足最短路,那么将两条路上的信息合并并更新k的信息,就可以得到新路径的值。只需要用Floyd再加上一部分优化就好,只是要想到用Floyd不容易,可能会想到用Dijkstra算法(可是不知道能不能过)
#include<bits/stdc++.h> using namespace std; int n,k,m,s,t,u,v,d,c[102],a[102][102],f[102][102]; int main() { cin>>n>>k>>m>>s>>t; for(int i=1;i<=n;i++) cin>>c[i]; for(int i=1;i<=k;i++) for(int j=1;j<=k;j++) cin>>a[i][j]; memset(f,60,sizeof(f)); for(int i=1;i<=m;i++) { cin>>u>>v>>d; if(!a[c[v]][c[u]]&&c[v]!=c[u])f[u][v]=min(f[u][v],d); if(!a[c[u]][c[v]]&&c[u]!=c[v])f[v][u]=min(f[v][u],d); } if(c[s]==c[t]) { cout<<-1; return 0; } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { if(i!=j) for(int k=1;k<=n;k++) { if(j!=k&&i!=k) f[i][j]=min(f[i][j],f[i][k]+f[k][j]); } } if(f[s][t]>999999)cout<<-1; else cout<<f[s][t]; return 0; }
20:52:02
我终于要认认真真看电视了!!!
以上是关于第三关 ——图论:最短路的主要内容,如果未能解决你的问题,请参考以下文章