本文出自我的掘金博客, 欢迎大家访问传送门
首先奉上一道模板题 传送门
我第一个要介绍的方法是dijikstra
算法, 算法的思想我想不必介绍了, 大家都知道
dijkstra的无优化版本核心代码
if(map[i][j]>map[i][k]+map[k][j])
map[i][j]=map[i][k]+map[k][j];
//其实就是一个松弛操作而已嘛,这就是Dijkstra与Floyd的核心思想,Dijkstra就是把时间复杂度降低,范围缩小而已
它的一个最简单的也可以大幅度提升性能的优化当然就是堆优化啦, 其实也没多难, 只用一个优先队列即可, 优先队列的底层实现就是堆, 直接调库, 可以节省手写堆的时间, 还可以避免出错 何乐而不为呢?
对于每一个加入队列的节点,我们考虑用pair来存。当然写结构体也可以,但是pair可以更加的方便。
typedef pair<int, int> pa;
priority_queue <pa, vector<pa>, cmp > q;
struct cmp
{
bool operator()(const pa p1, const pa p2)
{
return p1.second > p2.second; //second的小值优先
}
};
//这里注意要传入比较函数, 由于pair的第二个值代表着距离, 且要求小根堆, 故这样写cmp函数
接下来我们采用链式前向星来存储边, 至于为什么用这个? 因为我看见评论区神犇都在用, 肯定有其过人之处
第一步, 建立边结构体
struct Edge {
int next;
int to;
int w;
} e[100000]; //这里至少要开双倍那么大, 因为是无向图, 双向都要加边哒, 我在这里踩过坑
其中edge[i].to表示第i条边的终点,edge[i].next表示与第i条边同起点的下一条边的存储位置,edge[i].w为边权值.
此外, 我们还需要一个
head
数组, 它是用来表示以i为起点的第一条边存储的位置,实际上你会发现这里的第一条边存储的位置其实在以i为起点的所有边的最后输入的那个编号.你模拟一遍这个过程就会知道为什么, 因为后面的会覆盖前面的.
第二步, 写加边的函数, 我们需要一个初始值为0的变量cnt
.
void addEdge(int u, int v, int w) {
e[++cnt].w = w;
e[cnt].to = v;
e[cnt].next = head[u];
head[u] = cnt; //head[i]保存的是以i为起点的所有边中编号最大的那个,而把这个当作顶点i的第一条起始边的位置.
}
第三步, 写我们的dijkstra
函数, 注释已经很详细了
void dijkstra () {
priority_queue <pa, vector<pa>, cmp > q;//初始化优先队列
q.push(make_pair(s, 0)); //将源点放入优先队列
//初始化为一个很大的值
for (int i = 1; i <= n; i++) {
dis[i] = 2e9;
}
//将源点赋值为0
dis[s] = 0;
while (!q.empty()) {
int now = q.top().first;
int val = q.top().second;
q.pop();
//遍历每一个顶点相连的所有边, 并进行松弛操作
for (int i = head[now]; i; i = e[i].next) {
int to = e[i].to;
//松弛操作不解释
if (dis[to] > e[i].w + val) {
dis[to] = e[i].w + val;
q.push(make_pair(to, dis[to]));
}
}
}
}
最后附上Dijkstra
算法优化版本的完整代码
//打算尝试一下Dijkstra算法的链式前向星写法以及堆优化
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pa;
int n, m, s, t, head[10000], cnt, dis[10000];
//建立边结构体,其中edge[i].to表示第i条边的终点,edge[i].next表示与第i条边同起点的下一条边的存储位置,edge[i].w为边权值.
struct Edge {
int next;
int to;
int w;
} e[100000]; //这里至少要开双倍那么大, 因为是无向图, 双向都要加边哒
struct cmp
{
bool operator()(const pa p1, const pa p2)
{
return p1.second > p2.second; //second的小值优先
}
};
void addEdge(int u, int v, int w) {
e[++cnt].w = w;
e[cnt].to = v;
e[cnt].next = head[u];
head[u] = cnt; //head[i]保存的是以i为起点的所有边中编号最大的那个,而把这个当作顶点i的第一条起始边的位置.
}
void dijkstra () {
priority_queue <pa, vector<pa>, cmp > q;
q.push(make_pair(s, 0)); //将源点放入优先队列
for (int i = 1; i <= n; i++) {
dis[i] = 2e9;
}
dis[s] = 0;
while (!q.empty()) {
int now = q.top().first;
int val = q.top().second;
q.pop();
for (int i = head[now]; i; i = e[i].next) {
int to = e[i].to;
if (dis[to] > e[i].w + val) {
dis[to] = e[i].w + val;
q.push(make_pair(to, dis[to]));
}
}
}
}
int main() {
cin >> n >> m >> s >> t;
int u, v, w;
for (int i = 1; i <= m; i++) {
cin >> u >> v >> w;
addEdge(u, v, w);
addEdge(v, u, w);
}
dijkstra();
cout << dis[t];
return 0;
}
接下来介绍Bellman-Ford
算法
对于这个算法只是稍微优化了一下就取得了不错的性能
以下操作循环执行至多n-1次,n为顶点数:
对于每一条边e(u, v),如果Distant[u] + w(u, v) < Distant[v],则另Distant[v] = Distant[u]+w(u, v)。w(u, v)为边e(u,v)的权值;
若上述操作没有对Distant进行更新,说明最短路径已经查找完毕,或者部分点不可达,跳出循环。否则执行下次循环;
用代码描述就是
dis[s] = 0;
for (int i = 1; i < n; i++) {
check = true;
for (int i = 1; i <= m; i++) {
if (dis[v[i]] > dis[u[i]] + w[i]) {
check = false;
dis[v[i]] = dis[u[i]] + w[i];
}
if (dis[u[i]] > dis[v[i]] + w[i]) {
check = false;
dis[u[i]] = dis[v[i]] + w[i];
}
}
if (check) {
break;
}
}
敲重点, 其中check
的作用就是当没有可以松弛的点时, 就说明算法以及结束, 不用遍历n- 1
轮了, 直接跳出算法
下面附上完整代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2500 + 10;
const int MAXM = 6200 + 10;
const int INF = 2e9;
int u[MAXM], v[MAXM], dis[MAXN], w[MAXM];
bool check;
int n, m, s, t;
int main() {
cin >> n >> m >> s >> t;
for (int i = 1; i <= m; i++) {
cin >> u[i] >> v[i] >> w[i];
}
for (int i = 1; i <= n; i++) {
dis[i] = INF;
}
dis[s] = 0;
for (int i = 1; i < n; i++) {
check = true;
for (int i = 1; i <= m; i++) {
if (dis[v[i]] > dis[u[i]] + w[i]) {
check = false;
dis[v[i]] = dis[u[i]] + w[i];
}
if (dis[u[i]] > dis[v[i]] + w[i]) {
check = false;
dis[u[i]] = dis[v[i]] + w[i];
}
}
if (check) {
break;
}
}
cout << dis[t];
return 0;
}