最短路


求从 s 到 t 权值和最小的路径

Floyd 算法:
-
多源最短路,求出所有点对的最短路长度
-
时间复杂度: O(n³)

Dijkstra 算法:
-
单源最短路,求出某个点 s 到所有点的最短路长度
-
时间复杂度: O(n²)/O(mlogn)
-
无法处理负权

SPFA 算法,即队列优化的 Bellman-Ford 算法:
-
单源最短路,求出某个点 s 到所有点的最短路长度
-
时间复杂度:声称为 O(m) ,最坏 O(nm) ,容易卡到最坏
-
可以处理负权边,可以判断负权环

1:Floyd

int d[MAXN][MAXN];

void floyd() {
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i != j && i != k && j != k)
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
}
}
}


设 d[i][j][k] 为从 i 到 j ,仅通过编号为 1-k 的中间节点的最短路径距离

d[i][j][k]=min(d[i][j][k-1], d[i][k][k-1] + d[k][j][k-1])

初始值 d[i][j][0] 为两点之间边权值,未连通为 INF

从 1 到 n 枚举 k ,然后枚举 (i, j)

为了方便可以不开第三维,在原地迭代

2:Dijkstra:

未优化版:(自己写的可能错)

#include<bits/stdc++.h>
#define MAX 0x3f3f3f3f
using namespace std;
int n,m,s;
int ls1,ls2,ls3;
int dis[100005];
bool vis[100005];
int nn=0;
vector<pair<int,int> > vec[100005];
int main(){
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&ls1,&ls2,&ls3);
vec[ls1].push_back(make_pair(ls3,ls2));
}
for(int i=0;i<=n;i++)
dis[i]=MAX,vis[i]=false;
dis[s]=0;
for(int i=1;i<n;i++){
int x=0;
for(int j=1;j<=n;j++)
if(!vis[j]&&(x==0||dis[j]<dis[x]))x=j;
vis[x]=true;nn++;

int len=vec[x].size();
for(int j=0;j<len;j++)
if(dis[vec[x][j].second]>dis[x]+vec[x][j].first)
dis[vec[x][j].second]=dis[x]+vec[x][j].first;
}
for(int i=1;i<=n;i++){
printf("%d ",dis[i]);
}


return 0;
}

堆优化版:

int d[MAXN];
bool vis[MAXN];
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q;
int dijkstra(int s, int t){
memset(d, 0x3f, sizeof(d));
d[s] = 0;
q.push(make_pair(0, s));
while(!q.empty()){
int now = q.top().second;
q.pop();
if(!vis[now]){
vis[now] = true;
for(int i = he[now]; i; i = ne[i]){
Edge& e = ed[i];
if(d[e.to] > d[now] + e.dist){
d[e.to] = d[now] + e.dist;
q.push(make_pair(d[e.to], e.to));
}
}
}
}
return d[t] == INF ? -1 : d[t];
}


起点作为已访问集合的第一个点,并更新相连的点的 dis
-
找到未被访问的 dis 最小的点,标记访问,用这个点更新相连的点 dis

重复上述过程直到所有的点均被访问

问题在于“找到未被访问的 dis ” 最小的点 这一步,两种不同的实现方法会带来两种复杂度

枚举每个点

当点 u 的距离更新时,将 (dis[u], u) 插入堆中,这样堆中可能有多个 u ,此时取出后
面的点时,会发现 u 已经被访问过不再处理

3:SPFA:

bool inq[MAXN];
queue<int> q;
inline int spfa(int s, int t) {
q.push(s);
inq[s] = true;
memset(d, 0x3f, sizeof(d));
d[s] = 0;
while (!q.empty()) {
int now = q.front(); q.pop();
inq[now] = false;
for (int i = he[now]; i; i = ne[i]) {
Edge &e = ed[i];
if (d[now] + e.dist < d[e.to]) {
d[e.to] = d[now] + e.dist;
if (!inq[e.to]) {
q.push(e.to);
inq[e.to] = true;
}
}
}
}
return d[t] == INF ? -1 : d[t];
}


Bellman-Ford :对整张图进行 n-1 次松弛,每次枚举每条边进行松弛,最后一定能得出
最优解

SPFA :在上述过程中避免无意义的松弛

只有成功的松弛操作才会对那个点产生影响,所以使用队列维护等待松弛的点,每次取出
一个点进行松弛,对于所有松弛成功的点加入队列

判负环:某个点松弛了第 n 次,说明有负环

//当最短路为单向路,可以将入边和出边分开记录,以求出到一个点来和去的最短路径

原文地址:https://www.cnblogs.com/zyfltyyz/p/11719178.html