图论 _ 基本最短路算法
约定:
n是指点的数量,m是指边的数量
目录:
Dijkstra算法
Dijkstra算法只能用于所有边权均为非负数值的图
Dijkstra算法有两种实现:
一种是朴素实现,复杂度是 $O(n^2) $
一种是堆优化版,复杂度是 \(O(m \log n)\)
堆优化版+邻接表存图有以下优点:
- 不需要对重边做处理。
- 不需要对自环做处理
过程
补: 优先队列:带权值的队列
把优先队列的权值设为:离结点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;
}