图论 _ 基本最短路算法
约定:
n是指点的数量,m是指边的数量
目录:
Dijkstra算法
Dijkstra算法只能用于所有边权均为非负数值的图
Dijkstra算法有两种实现:
一种是朴素实现,复杂度是
一种是堆优化版,复杂度是
堆优化版+邻接表存图有以下优点:
- 不需要对重边做处理。
- 不需要对自环做处理
过程
补: 优先队列:带权值的队列
把优先队列的权值设为:离结点1的距离
如果某些结点已经在优先队列,不用删除,只需要再放入一个带新权值的该结点即可。
如上,已经走过的点不需要再入队,记得更新parent数组
优先队列中出队元素,如果已经走过,直接删除即可(不做任何操作)
代码
#include<bits/stdc++.h> using namespace std; #define ll long long const ll N=1e5 + 5; typedef pair<ll, ll> PII; //因为优先队列用结构体创建需要设置的东西太多,所以使用pair //而且必须是第一个结点代表:路径长度,第二个结点代表结点数 //原因: 优先队列中greater<PII>排序默认对第二个结点排序 //如果反着放需要重写operator函数 ll n,m,x,y; ll visited[N];//是否访问过某结点 long long dist[N];//某结点距离 struct Node { ll value; ll len; }; vector<Node> g[N];//图 //堆优化版dijksyta ll dijkstra() { fill(dist, dist + N, 1e18);//距离默认无穷大 priority_queue<PII, vector<PII>, greater<PII> > q; q.push({0, x});//传入首结点 dist[x] = 0;//头结点距离为0 while (q.size()) { PII t = q.top(); q.pop(); ll distance = t.first; ll ver = t.second; if (visited[ver]) continue; visited[ver] = 1; for(auto i:g[ver]) // 遍历ver的邻接点 { ll v=i.value; ll k=i.len; ll distan = distance+k; if (dist[v] > distan) { dist[v] =distan; q.push( {dist[v],v} ); } } } return dist[y]; } int main() { memset(visited,0,sizeof visited); cin>>n>>m>>x>>y; while(m--) { ll a,b; ll k; cin>>a>>b>>k; g[a].push_back({b,k}); g[b].push_back({a,k}); } ll ans=dijkstra(); if(ans==1e18)ans=-1; cout<<ans<<endl; return 0; }
模板
重写operartor示意
struct Node{ int vec; int dis; bool operator < (const Node &t) const{ return dis<t.dis; } };
dij模板
int n, m; vector<pii> g[N]; int vis[N]; int dist[N]; void dij(int be, int en) { memset(dist, 0x3f, sizeof dist); priority_queue<pii, vector<pii>, greater<pii>> q; q.push({0, be}); dist[be] = 0; while (q.size()) { pii k = q.top(); q.pop(); if (vis[k.second]) continue; vis[k.second] = 1; for (pii t : g[k.second]) { int cnt = dist[k.second] + t.first; if (dist[t.second] > cnt) { dist[t.second] = cnt; q.push({cnt, t.second}); } } } if (dist[en] <= 0x3f3f3f3f) cout << dist[en] << endl; else cout << -1 << endl; }
输出路径模板
#include <bits/stdc++.h> using namespace std; const int N = 510; int n, m, st, en, val[N], cnt[N], pre[N], dist[N], tot[N]; typedef pair<int, int> pii; vector<pii> vec[N]; int vis[N]; void dj() { me memset(dist, 0x3f, sizeof dist); priority_queue<pii, vector<pii>, greater<pii>> q; q.push({0, st}); pre[st] = -1;//记得初始化 dist[st] = 0; while (q.size()) { pii k = q.top(); q.pop(); if (vis[k.second]) continue; vis[k.second] = 1; for (pii t : vec[k.second]) { int dis = t.first + dist[k.second]; if (dist[t.second] > dis) { dist[t.second] = dis; pre[t.second] = k.second;//更新路径 q.push({dis, t.second}); } else if(dist[t.second]==dis) {//用于输出最短长度的经过结点最多的路径,如果不需要删除即可。 cnt[t.second]++ dist[t.second] = dis; pre[t.second] = k.second; q.push({dis, t.second}); } } } cout << dist[en] << endl; } //将路径逆置 stack<int> ans; void print(int en) { if (pre[en] == -1) { ans.push(en-1); return; } else { ans.push(en-1); print(pre[en]); } } signed main() { cin >> n >> m >> st >> en; en++; st++; for (int i = 1; i <= n; i++) cin >> val[i]; for (int i = 1; i <= m; i++) { int a, b, c; cin >> a >> b >> c; a++, b++; vec[a].push_back({c, b}); vec[b].push_back({c, a}); } dj(); cout << cnt[en] << " " << tot[en] << endl; print(en); //输出路径 while(ans.size()){ if(ans.size()==1){ cout<<ans.top()<<endl; }else cout<<ans.top()<<" "; ans.pop(); } return 0; }
实战
D - Train( AtCoder - abc192_e)
#include<bits/stdc++.h> using namespace std; #define ll long long const ll N=1e5 + 5; typedef pair<ll, ll> PII; // 定义一个二元组PII,第一维:距离,第二维:点 /* *第一维用.first引用; *第二维用.second引用 */ ll n,m,x,y; ll visited[N]; long long dist[N];//距离 struct Node { ll v; ll t,k; //城市v //花费时间t //发车时间k }; vector<Node> g[N]; //堆优化版dijksyta ll dijkstra() { fill(dist, dist + N, 1e18); // 初始化距离为无穷 priority_queue<PII, vector<PII>, greater<PII> > q; // 定义一个按照距离从小到大排序的优先队列,第一维:距离,第二维:点 /* *第一个是数据类型int,char.. *第二个是储存结构,直接填vector<前面数据类型> *第三个是按升序还是降序排列greater是升序 */ dist[x] = 0; // 源点x 距离为0 q.push({0, x}); // 把源点x信息放入优先队列 while (q.size()) { PII t = q.top(); ll distance = t.first; // 最小距离 ll ver = t.second; // 相对应的点 q.pop(); // 顶层元素(权值最小元素)出队 if (visited[ver]) continue; // 保证每个点只出入队一次, visited[ver] = 1; // 标记,因为dijkstra的贪心策略保证每个点只需要进出队一次 for(auto i:g[ver]) // 遍历ver的邻接点 { ll v=i.v; ll t=i.t, k=i.k; ll distan = (distance+k-1)/ k * k +t; if (dist[v] > distan) { dist[v] =distan; q.push( {dist[v],v} ); // 这里不需要判断visited,因为一旦更新发现更小必须放入队列 } } } return dist[y]; } int main() { memset(visited,0,sizeof visited); cin>>n>>m>>x>>y; while(m--) { ll a,b; ll t,k; cin>>a>>b>>t>>k; g[a].push_back({b,t,k}); g[b].push_back({a,t,k}); } ll ans=dijkstra(); if(ans==1e18)ans=-1; cout<<ans<<endl; return 0; }
多源最短路 Floyed算法
思想
我们知道:从任意节点A到任意节点B的最短路径只有两种情况:
- 1是直接从A到B
- 2是从A经过若干个节点到B。
所以我们只需要将所有情况的中间路径遍历一边,取dist[i][j] =min( dist[i][j] ,dist[i][k] + dist[k][j] );
即可。
对于如何遍历我们可以先假设只有一个中间节点:
假如现在只允许经过1号顶点,求任意两点之间的最短路程,应该如何求呢?只需判断dis[i][1]+dis[1][j]是否比dis[i][j]要小即可。(dis[i][j]表示的是从i号顶点到j号顶点之间的路程。dis[i][1]+dis[1][j]表示的是从i号顶点先到1号顶点,再从1号顶点到j号顶点的路程之和。) 其中i是1n循环,j也是1n循环 。
代码:
for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { dis[i][j]=min(dis[i][j],dis[i][1]+dis[1][j]); } }
如果中间节点是1和2两个节点呢?
自然想到:
//经过1号顶点 for(i=1;i<=n;i++) for(j=1;j<=n;j++) dis[i][j]=min(dis[i][j],dis[i][1]+dis[1][j]); //经过2号顶点 for(i=1;i<=n;i++) for(j=1;j<=n;j++) dis[i][j]=min(dis[i][j],dis[i][2]+dis[2][j]);
上面代码看着很美好,但是对吗?
对于上图A到B的最小路径是A->B->C->D ,上面代码可以得到正确结果吗?(设C为1号节点,D为2号节点)
当然可以,因为:第一次双重循环可以找到B到D的最短k路径B-C-D,而第2次双重循环可以找到B-(—C—)—D—A这条路
所以将代码一般化就是
for(int k=1; k<=n; k++) //插入k点 { for(int i=1; i<=n; i++) { for(int j=1; j<=n; j++) { if(d[i][k]<INF&&d[k][j]<INF) //消除加法溢出问题 d[i][j]=min(d[i][j],d[i][k]+d[k][j]); //更新两点距离 } } }
这里再纠正一个错误:为什么不讲插入节点放入最里面的循环,而是放在最外层。
还是上面的例子
如果我们在最内层检查所有节点K,那么对于A->B,我们只能发现一条路径,就是A->B;显然是错误的。
代码
#include<stdio.h> #include<iostream> #include<limits.h> #include<string.h> #include<algorithm> using namespace std; const int INF=INT_MAX/100; int d[3000][3000],n,m; void floyed() { for(int k=1; k<=n; k++) //插入k点 { for(int i=1; i<=n; i++) { for(int j=1; j<=n; j++) { if(d[i][k]<INF&&d[k][j]<INF) //消除加法溢出问题 d[i][j]=min(d[i][j],d[i][k]+d[k][j]); //更新两点距离 } } } } int main() { cin>>n>>m; for(int i=0; i<=n; i++) { for(int j=0; j<=n; j++) if(i==j) d[i][j]=0; else d[i][j]=INF; } for(int i=0; i<m; i++) { int sta,stb,coc; cin>>sta>>stb>>coc; d[sta][stb]=coc; d[stb][sta]=coc; } floyed(); int ans=INF; int ansid=0; for(int i=1;i<=n;i++){ int cnt=0; for(int j=1;j<=n;j++){ cnt=max(cnt,d[i][j]); } if(cnt!=INF){ if(ans>cnt){ ans=cnt; ansid=i; } } } if(ans==INF) cout<<"0"<<endl; else cout<<ansid<<" "<<ans<<endl; return 0; }
本文作者:kingwzun
本文链接:https://www.cnblogs.com/kingwz/p/16554387.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步