目录
Dijkstra迪杰斯特拉算法
迪杰斯特拉算法(Dijkstra)是由迪杰斯特拉于1959年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。
缺点:在负权图中无法使用(SPFA,Bellman_Ford皆可)
可行性证明
dis[i]表示点i到起点的距离。
队列中存在若干点,其中dis[i]最小。
假设i出队后,并不是最短路。即存在一点j使得dis[i]变得更小,则有:dis[i]>dis[j]+w。
又因为dis[j]>=dis[i],当w>0时,该不等式不成立。因此点i出队后,一定找到i到起点的最短路。
写法
核心:根据已知最短路径来更新未知点
在BFS中定义小根堆优先队列,并且利用pair建立两个变量(点n距离1的值,点n)

1. 初始化所有点到起点1的距离为正无穷,且dis[1]=0。
2. 将起点信息(0,1)入队进行bfs拓展,将起点相连点入队,即(3,2),(5,3)入队。更新点2,3的距离信息。
3. 继续拓展节点2,3,将(6,4),(9,4)以及(119,5)入队。此时队首为(5,4),将该节点出队,得到dis[4]=5。
4. 继续拓展,直到终点出队,BFS循环结束
时间复杂度
朴素写法:O(n^2)
优化写法:O((n+m) logm)
例题
描述
给定一个n个点m条边的有向图,图中可能存在重边和自环,所有边权均为正值(边权小于10000)。
请你求出1号点到n号点的最短距离。如果无法从1号点走到n号点,则输出-1。
输入描述
第一行包含整数n和m(n<=1e5,m<=2e5)。
接下来m行每行包含三个整数x,y,z,表示存在一条从点x到点y的有向边,边长为z。
输出描述
输出一个整数,表示1号点到n号点的最短距离
如果路径不存在,则输出-1。
样例
输入用例
3 3
1 2 2
2 3 1
1 3 4
输出用例
3
写法
本题如果用朴素的dijkstra算法,按O(n^2)算的话,1e5*1e5一定会超时,故用优化算法
上优化后算法
#include<bits/stdc++.h>
using namespace std;
#pragma GCC s
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC fast
//优化程序运行效率
void read(int &x) {
x=0;
int flag=1;
char ch=getchar();
for(; ch<'0'||ch>'9';) {
if(ch=='-') flag=-1;
ch=getchar();
}
for(; ch>='0'&&ch<='9'; ch=getchar()) x=x*10+ch-'0';
x=x*flag;
}//快读进行常数优化
const int N=1e5+5;
int dis[N],n,m;
int h[N],nx[N*2],v[N*2],w[N*2],tot;
bool vis[N];
//n*2原型:n*(n-1)=m,n为节点数,m为一个图的最大边数
priority_queue <pair <int,int> ,vector<pair <int,int> >,greater<pair <int,int> > > q;
/*
优先队列大根堆写法:priority_queue < pair<int,int> > q;
优先队列小根堆写法:priority_queue <pair <int,int> ,vector<pair <int,int> >,greater<pair <int,int> > > q;
*/
void add(int x,int y,int z) {
tot++;
v[tot]=y;
w[tot]=z;
nx[tot]=h[x];
h[x]=tot;
}//邻接表记录图的关系
void dijkstra(int x) {
memset(dis,0x3f,sizeof dis);
/*
1. 首先,memset()是按内存地址命名的,故用0x3f;
2. memset()中的0x3f赋完值后,dis[]中的数字是为0x3f3f3f3f,而int的最大值为0x7f7f7f7f,
为了防止两个标记的数组值相加导致爆int,故对半开为0x3f3f3f3f
*/
dis[x]=0;//出发点到出发点的距离是0
q.push({0,x});//放入队列
while(!q.empty()) {//队列为空时代表遍利完成
pair <int,int> tmp;
tmp=q.top();//取得队列的第一位
q.pop();//弹出队列的第一位
vis[tmp.second]=1;//vis标记,防止二次入队,降低时间复杂度
for(int i=h[tmp.second]; i; i=nx[i]) {
if(vis[v[i]]==0/*vis标记检查(==0代表没入队),防止二次入队,降低时间复杂度 */&&dis[v[i]]>dis[tmp.second]+w[i]) {
dis[v[i]]=dis[tmp.second]+w[i];
q.push({dis[v[i]],v[i]});//入队
}
}
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
//优化程序
read(n);
read(m);
for(int i=1; i<=m; i++) {
int x;
int y;
int z;
read(x);
read(y);
read(z);
add(x,y,z);
}
dijkstra(1);//进行遍历
if(dis[n]==0x3f3f3f3f) cout<<-1;//如果没有被访问证明不联通,故输出-1
else cout<<dis[n];
return 0;
}
Spfa算法
SPFA就是由无权图的BFS转化而来。在无权图中,BFS首先到达的顶点所经历的路径一定是最短路(也就是经过的最少顶点数),所以此时利用数组记录节点访问可以使每个顶点只进队一次,但在带权图中,最先到达的顶点所计算出来的路径不一定是最短路。一个解决是放弃数组,此时所需时间自然就是指数的,所以我们不能放弃数组,而是在处理一个已经在队列中且当前所得的路径比原来更好的顶点时,直接更新最优解。
缺点:在菊花图(下图)中,时间复杂度飘升至O(mn)(m为边数,n为点数)与Bellman_Ford的时间复杂度相同,也不能无法解决限制变数的问题(Bellman_Ford 可以)
不过在正常情况下时间复杂度为O(km) (在稀疏图中一般小于等于2)

写法

在求解各点到起点的最小距离dis值时,若某点i产生一个更小的dis[i],那么节点i后续指向的节点都会重新更新,因此我们可以将该点i再次入队,重新更新即可。
例题
描述
给定一个n个点m条边(n<=1e5,m<=2e5)的有向图,图中可能存在重边和自环,边权绝对值小于104。数据保证图中不存在负权回路。
请你求出1号点到n号点的最短距离。如果无法从1号点走到n号点,则输出-1。
输入描述
第一行包含整数n和m(n<=1e5,m<=2e5)。
接下来m行每行包含三个整数x,y,z,表示存在一条从点x到点y的有向边,边长为z。
输出描述
输出一个整数,表示1号点到n号点的最短距离
如果路径不存在,则输出-1。
样例
输入用例
3 3
1 2 1
2 3 2
1 3 1
输出用例
1
写法
#include<bits/stdc++.h>
u

迪杰斯特拉算法,Spfa算法,贝尔曼-福特算法,弗洛伊德算法超详代码注释+例题使用CC++&spm=1001.2101.3001.5002&articleId=140671247&d=1&t=3&u=b94bf245d7c0477088da8c6b98339459)
2370

被折叠的 条评论
为什么被折叠?



