几种最短路算法模板
1.Dijkstra
从起点开始,每次选取距离起点最近且未被访问过的点,更新最短路径,直到所有点到起点的最短路径确认
朴素dijkstra,时间复杂度 O(n^2):
//Dijkstra算法
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<vector>
#include<string>
#define Inf 0x3f3f3f3f//极大值
#define maxn 1010
using namespace std;
int n, m;//点,边
int start, goal;//起点,终点
int map[maxn][maxn];//图
int vis[maxn], dis[maxn];//是否访问,距离
void init()
{
for (int i = 1; i <= n; i++)
dis[i] = map[start][i];
}
void dijkstra()
{
init();
int minn;//记录每趟最小路径值
int pos;//记录最短路下标
vis[start] = 1;
for (int i = 1; i <= n; i++)
{
minn = Inf;
for (int j = 1; j <= n; j++)//找到相连最小边权点,为第pos个点
{
if (!vis[j] && dis[j] < minn)
{
minn = dis[j];
pos = j;
}
}
vis[pos] = 1;
for (int j = 1; j <= n; j++)//更新距离
{
if (!vis[j] && dis[j] > dis[pos] + map[pos][j])
dis[j] = dis[pos] + map[pos][j];
}
}
}
int main()
{
cin >> n >> m;
cin >> start >> goal;
memset(map, Inf, sizeof(map));
int u, v, w;
for (int i = 1; i <= m; i++)
{
cin >> u >> v >> w;
map[u][v] = w;
map[v][u] = w;
}
dijkstra();
cout << dis[goal] << endl;
}
为了达到更高的效率
堆优化后的dijkstra,时间复杂度 O(m*logn):
//Dijkstra_Optimized算法
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<vector>
#include<string>
#define Inf 0x3f3f3f3f//极大值
#define maxn 10010
using namespace std;
struct edge
{
int to;//终点
int dis;//权值
int next;//下一条边
//不添加from起点,意义不大
};
struct Node
{
int dis;//权值
int pos;//下标
bool operator <(const Node& x)const
{
return x.dis < dis;//从小到大排序
}
};
edge e[maxn];
int head[maxn], dis[maxn], cnt, vis[maxn];
int n, m, s;
priority_queue<Node>q;//每次出队的都是dis最小的,节省了(更新后再比,出现更小的再更新此类情况)消耗的时间
void addedge(int u, int v, int w)
{
cnt++;
e[cnt].dis = w;
e[cnt].to = v;
e[cnt].next = head[u];//head[u]总是临时存放着同起点的前一条边,当同起点的后一条边输入时,把前一条边的index赋值给这条边的next(于是同起点的边顺序是输入顺序的反序)
head[u] = cnt;
}
void Dijkstra()
{
fill(dis, dis + n + 1, Inf);
dis[s] = 0;
q.push({ 0, s });//加入起点
while (!q.empty())
{
Node tmp = q.top();
q.pop();
int x = tmp.pos, d = tmp.dis;
if (!vis[x])continue;
vis[x] = 1;
for (int i = head[x]; i; i = e[i].next)
{
int y = e[i].to;
if (dis[y] > dis[x] + e[i].dis)
{
dis[y] = dis[x] + e[i].dis;
q.push({ dis[y], y });
}
}
}
}
int main()
{
cin >> n >> m >> s;
for (int i = 1; i <= m; i++)
{
int u, v, w;
cin >> u >> v >> w;
addedge(u, v, w);
}
Dijkstra();
for (int i = 1; i <= n; i++)
cout << dis[i] << ' ';
}
2.Floyed
对于一个图map,遍历每一个节点作为中间节点k,对于每一个节点k,比较map[i][k]+map[k][j]和map[i][j]的较小值,更新map[i][j]为较小值,这样map[i][j]就成为任意两点的最短路
可以求多源最短路,时间复杂度 O(n^3):
//Floyd算法
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<vector>
#include<string>
#define Inf 0x3f3f3f3f//极大值
#define maxn 1010
using namespace std;
int n, m;//点,边
int start, goal;//起点,终点
int map[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 (map[i][j] > map[i][k] + map[k][j])
map[i][j] = map[i][k] + map[k][j];
}
int main()
{
cin >> n >> m;
cin >> start >> goal;
memset(map, Inf, sizeof(map));
for (int i = 1; i <= n; i++)
map[i][i] = 0;
int u, v, w;
for (int i = 1; i <= m; i++)
{
cin >> u >> v >> w;
map[u][v] = w;
map[v][u] = w;
}
floyd();
cout << map[start][goal]<<endl;
}
3.SPFA
SPFA是Bellman-Ford的队列优化,思想同BFS
可以判断负权边,时间复杂度 O(m)~ O(n*m)
SPFA矩阵实现:
//SPFA算法
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<vector>
#include<string>
#define Inf 0x3f3f3f3f//极大值
#define maxn 2510
using namespace std;
int n, m;//点,边
int start, goal;//起点,终点
int map[maxn][maxn];//图
int vis[maxn],dis[maxn];//是否在队中,距离
int cnt[maxn];//记录顶点入队次数
void init()
{
memset(dis, Inf, sizeof(dis));
memset(map, Inf, sizeof(map));
}
bool spfa()
{
queue<int>q;
dis[start] = 0;
q.push(start);
cnt[start]++;
vis[start] = 1;
while (!q.empty())
{
int temp = q.front();
q.pop();
vis[temp] = 0;
for (int i = 1; i <= n; i++)
{
if (dis[i] > dis[temp] + map[temp][i])
{
dis[i] = dis[temp] + map[temp][i];
if (!vis[i])
{
q.push(i);
cnt[i]++;
vis[i] = 1;
if (cnt[i] > n)
return 0;
}
}
}
}
return 1;
}
int main()
{
cin >> n >> m;
cin >> start >> goal;
init();
int u, v, w;
for (int i = 1; i <= m; i++)
{
cin >> u >> v >> w;
map[u][v] = w;
map[v][u] = w;
}
int result = spfa();
if (!result)
cout << "存在负环" << endl;
else
cout << dis[goal] << endl;
}
SPFA邻接表实现(推荐):
//SPFA算法
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<vector>
#include<string>
#define Inf 0x3f3f3f3f//极大值
#define maxn 10100
using namespace std;
int n, m;//点,边
int start, goal;//起点,终点
int vis[maxn],dis[maxn];//是否在队中,距离
int cnt[maxn];//记录顶点入队次数
typedef struct Edge {
int to;//边的终端节点
int w;//边权值。
Edge(int to, int w) :to(to), w(w) {}//构造函数
}Edge;
vector<Edge> graph[maxn];//用vector模拟邻接表
void init() {
memset(cnt, 0, sizeof(cnt));
memset(dis, Inf, sizeof(dis));
memset(vis, false, sizeof(vis));
}
bool spfa()
{
queue<int> q;
dis[start] = 0;
q.push(start);
cnt[start]++;
vis[start] = 1;
while (!q.empty())
{
int temp = q.front();
q.pop();
vis[temp] = 0; //出队则false。
int v, w;
int t = graph[temp].size();//避免多次调用此函数。
//松弛操作
for (int i = 0; i < t; i++)
{
v = graph[temp][i].to;
w = graph[temp][i].w;
if (dis[v] > dis[temp] + w)
{
dis[v] = dis[temp] + w;//更新最短路径
if (!vis[v])
{
q.push(v);
cnt[v]++;
vis[v] = 1;
if (cnt[v] > n) { return 0; }
}
}
}
}
return 1;
}
int main()
{
int u, v, w;
init();
cin >> n >> m;
cin >> start >> goal;
for (int i = 0; i < m; i++)
{
cin >> u >> v >> w;
graph[u].push_back(Edge(v, w));
}
int result = spfa();
if (!result)
cout << "存在负环" << endl;
else
cout << dis[goal] << endl;
return 0;
}
注意事项
由于SPFA时间复杂度不稳定,可能在实际解题中过不去,如洛谷P4779,所以在解决单源最短路时,比赛者似乎习惯用更稳定的堆优化Dijkstra解题