单源最短路
描述:求1号到n号最短路
适用范围:只有一个起点
算法 | 适用范围 | 边权值 | 复杂度(N:点数 M:边数) | 来源 |
---|---|---|---|---|
朴素Dijkstra | 稠密图 | + | O(N^2) | 贪心 |
堆优化Dijkstra算法 | 稀疏图 | + | O(mlogn) | 贪心 |
Bellman_Ford算法 | - | O(nm) | 离散数学 | |
SPFA | - | 一般O(m),最坏O(nm) |
朴素Dijkstra算法
算法描述
- dis[st] = 0,dis[i] = +无穷
- for(i : n) // S:当前已确定最短的点
将不在S中的最近的点t加入S
t – > S
用t更新其他点的距离 dis[x] > dis[t] + w
代码实现
int dijkstra(){
memset(dis, 0x3f, sizeof dis);
//把所有距离初始化为正无穷
dis[1] = 0;
//起点记为0
for(int i = 0; i < n; i ++){
int t = -1;
for(int j = 1; j <= n; j ++){//找到集合外距离最近的点t
if(!st[j] &&(t == -1 || dis[t] > dis[j])){
t = j;
}
}
st[t] = true;//把t加到集合中去
for(int j = 1; j <= n; j ++){//用t更新其他的点
dis[j] = min(dis[j], dis[t] + g[t][j]);
}
}
if(dis[n] == 0x3f3f3f3f) return -1;
return dis[n];
}
堆优化Dijkstra算法
算法描述
每轮寻找最小的点可以用堆来实现 O(1)
堆中修改一个数时间复杂度 O(logn)
一共修改m次 O(mlogn)
堆的实现
1.手写堆 可以修改
2.优先队列 不支持修改,因此每次可以新写一个加入,问题冗余
代码实现
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
const int N = 150010;
int n, m;
int h[N], w[N], ne[N], e[N], idx;
bool st[N];
int dis[N];
void add(int x, int y, int z)
{
w[idx] = z;
e[idx] = y;
ne[idx] = h[x];
h[x] = idx ++;
}
int dijkstra()
{
memset(dis, 0x3f, sizeof dis);
dis[0] = 1;
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, 1});//0为距离,1为起点
while(heap.size())
{
PII k = heap.top();
heap.pop();
int ver = k.second, distance = k.first;
if(st[ver]) continue;
st[ver] = true;
for(int i = h[ver]; i != -1; i = ne[i])//用当前点更新其他点
{
int j = e[i];
if(dis[j] > distance + w[i]){
dis[j] = distance + w[i];
heap.push({dis[j], j});
}
}
}
if(dis[n] == 0x3f3f3f3f) return -1;
else return dis[n];
}
Bellman-Ford算法
算法描述
for n 次
备份
for 所有边 a,b,w
dis[b] = min(dis[b], dis[a] + w) //松弛操作
代码实现
const int N = 510, M = 10010;
struct Edge {
int a;
int b;
int w;
} e[M];//把每个边保存下来即可
int dist[N];
int back[N];//备份数组防止串联
int n, m, k;//k代表最短路径最多包涵k条边
int bellman_ford() {
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < k; i++) {//k次循环
memcpy(back, dist, sizeof dist);
for (int j = 0; j < m; j++) {//遍历所有边
int a = e[j].a, b = e[j].b, w = e[j].w;
dist[b] = min(dist[b], back[a] + w);
//使用backup:避免给a更新后立马更新b, 这样b一次性最短路径就多了两条边出来
}
}
if (dist[n] > 0x3f3f3f3f / 2) return -1;
else return dist[n];
}
SPFA算法
算法描述
首先:将起点–>queue
while(queue 不空)
① t <-- q.front()
q.pop()
② 更新所有t的出边t w -->b
代码实现
const int N = 100010;
int n, m;
int h[N], e[N], ne[N], w[N], dis[N], cur;
bool st[N];
void add(int a, int b, int c)
{
e[cur] = b;
w[cur] = c;
ne[cur] = h[a];
h[a] = cur ++;
}
int spfa()
{
memset(dis, 0x3f, sizeof dis);
dis[1] = 0;
st[1] = true;
queue<int> q;
q.push(1);
while(q.size())
{
int t = q.front();
q.pop();
st[t] = false;
for(int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if(dis[j] > dis[t] + w[i])
{
dis[j] = dis[t] + w[i];
if(!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}
if(dis[n] == 0x3f3f3f3f) return -1;
else return dis[n];
}
多源汇最短路
有许多不同的起点
算法 | 复杂度 | 来源 |
---|---|---|
Ford算法 | O(N^3) | DP |
Floyd算法
for(int k = 0; k <= n; k ++)
for(int i = 0; i <= n; i ++)
for(int j = 0; j <= n; j ++)
e[i][j] = min(e[i][j], e[i][k] + e[k][j]);
}
总结
用一句话总结:单源最短路优先使用SPFA,SPFA通不过使用堆优化Dijkstra,多源最短路径使用Ford。