定义
松弛:更新本节点与其他节点的距离(更小或更大)
前言
我没有写Bell算法,我认为你只要会SPFA就行了
代码只有关键的
Floyd算法
思想
暴力枚举两个节点
i
,
j
i,j
i,j的中间节点
k
k
k,(
i
i
i通过
k
k
k后可以到达
j
j
j)
时间复杂度
O
(
n
3
)
O(n^3)
O(n3)
特点
- 时间复杂度高
- 可以处理多源点的问题(多个起点)
Code
memset(dis, 0x3f, sizeof(dis));
for(int k = 1; k <= n; k ++){//枚举中继节点
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= n; j ++){
if( i != j && j != k && i != k ){
if( dis[i][j] > dis[i][k] + dis[k][j] )//松弛
dis[i][j] = dis[i][k] + dis[k][j];
}
}
}
}//dis[i][j]表示i到j的最短距离
Dijkstra算法
思想
- 固定一个源点,找到与这个点连接的值最小的且未被标记的边以及对应的点
- 对这个对应的点打上标记
- 用这条边对其他没有标记过的点进行松弛操作
特点
- 一次只能解决单源点路径(你也可以多次用啊)
- 负环会爆炸
- 复杂度 ( n 2 ) (n^2) (n2)
Code
//area[i][j]表示i到j的距离(邻接矩阵)
inline void Dijkstra(int x){
memset(v, 0, sizeof(v));
dis[x] = 0;
v[x] = 1;
for(int i = 1; i <= n; i ++){
int m_i_n = INF;
for(int j = 1; j <= n; j ++){
if( dis[j] < m_i_n && !vis[j] ){//找最小边
m_i_n = dis[j];
x = j;
}
}
vis[x] = 1;//标记
for(int j = 1; j <= n; j ++){
if( !vis[j] && dis[j] > dis[x]+area[x][j] )//松弛
dis[j] = dis[x]+area[x][j];
}
}
}
玄学优化
不要看字面意思“堆优化”,其实不难
我们可以发现,我们一直都在寻找最小边,如果遍历一波时间就会大大上涨,有没有办法将找边的时间复杂度降为
O
(
1
)
O(1)
O(1)呢?
当然有,优先队列小根堆嘛,它自动排序,而因为要存点,直接存pair类型不就行了?
优化之后时间直降
(
n
l
o
g
n
)
(nlogn)
(nlogn),怎么样,是不是连SPFA都不想学了
优化Code
#define Pair pair<int,int>
struct node{
int to, weight;
};
inline Dijkstra_2(int x){
priority_queue< Pair, vector<Pair>, greater<Pair> > q;//优先队列小根堆
memset(v, 0, sizeof(v));
memset(dis, 0x3f, sizeof(dis));
dis[x] = 0;
q.push(make_pair(0, x));//前一个是距离,后一个是点
while( !q.empty() ){
Pair t = q.top();
q.pop();
if( v[t.seond] )
continue;
v[t.seond] = 1;
for(int i = 0; i < G[t.second].size; i ++){
int son = G[t.second][i].to;
if( dis[son] > dis[t.seond]+G[t.second][i].weight ){//松弛
dis[son] = dis[t.seond]+G[t.second][i].weight;
if( !v[son] )
q.push(make_pair(dis[son], son));
}
}
}
}
SPFA
思想
我们用数组 d i s dis dis记录每个结点的最短路径估计值,用邻接表来存储图 G G G。我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点 u u u,并且用 u u u点当前的最短路径估计值对离开 u u u点所指向的结点 v v v进行松弛操作,如果 v v v点的最短路径估计值有所调整,且 v v v点不在当前的队列中,就将 v v v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止
操作
建立一个队列,初始时队列里只有起始点,再建立一个数组记录起始点到所有点的最短路径(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。然后执行松弛操作,用队列里有的点作为起始点去刷新到所有点的最短路,如果刷新成功且被刷新点不在队列中则把该点加入到队列最后。重复执行直到队列为空。
特点
- 无法处理带负环的图
- 期望的时间复杂度 O ( k e ) O(ke) O(ke), 其中 k k k为所有顶点进队的平均次数,可以证明k一般小于等于 2 2 2。
Code
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define M 100005
struct node{
int to, no;
node(int V, int W) : to(V), no(W) {};
};
vector<node>G[M];
int n, m, s;
int u, v, w;
int dis[M];
bool vis[M];
inline void SPFA(){
queue<int>q;
dis[s] = 0;
vis[s] = 1;
q.push(s);
while( !q.empty() ){
int t = q.front();
q.pop();
vis[t] = 0;
int siz = G[t].size();
for(int i = 0; i < siz; i ++){
int son = G[t][i].to;
if( dis[son] > dis[t] + G[t][i].no ){
dis[son] = dis[t] + G[t][i].no;
if( !vis[son] ){
vis[son] = 1;
q.push(son);
}
}
}
}
}
int main(){
scanf("%d%d%d", &n, &m, &s);
for(int i = 1; i <= m; i ++){
scanf("%d%d%d", &u, &v, &w);
G[u].push_back(node(v, w));
}
for(int i = 0; i <= n; i ++)
dis[i] = 2147483647;
SPFA();
for(int i = 1; i <= n; i ++)
printf("%d ", dis[i]);
return 0;
}