dijkstra
前言:
这个算法有前提,就是没有负权边
算法思想:
我们假设已经有一些点求的最短路,那么对于当前点 ,它的最短路要么是从起点直接走来(和起点有连边),要么就是已求得最短路得点中,距离最小得那个点走来。
算法描述:
该算法有两个重要数组,一个是dist[]数组用存节点 i 到起点的最短距离,初始为无穷大。
一个是v[]数组表示节点是否以求得最短路径。(专业点的叫打上永久标记)
1、首先我们将起点 s 打上永久标记。这个好理解,起点到起点最短距离为0 嘛,这个是确定的。那么我们初始
2、我们用最新打上永久标记的点 m 去更新未打上标记的点 i ,若从点 m 到 点 i 能使dist[i] 更小 ,那么更新 (这里暂且用邻接矩阵存图),
其实可以这么写
3、在更新过程中 我们需要记录一下 dist 最小的那个节点,并将其打上标记。
4、重复 2 和 3 n-1 次(每次能确定一个点的最短路径)。
朴素版dijkstra代码:
之所以朴素是因为找最小值是暴力找的
有一些细节就是,有向图就建单边,无向图就建双边
重边只存最小的那条
若边权值极值不是很大,就可以 用 0x3f3f3f(十六进制形式)来表示无穷大,因为 0x3f3f3f3f这个正好很大,而且0x3f3f3f3f + 0x3f3f3f3f也在 int 范围内 符合无穷加无穷也为无穷的性质,比较好处理。反正注意题目边权值范围就可。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4+10;
int G[N][N];//邻接矩阵存图,空间开销很大
int dist[N],v[N];
int n,m,s;
void init()
{
memset(dist,0x3f,sizeof dist);
memset(v,false,sizeof v);
memset(G,0x3f,sizeof G);
}
//朴素版dijkstra
void dijkstra(int x)
{
dist[x] = 0;
v[x] = true;
for(int i = 1;i < n;i ++)
{
int mn = 0x3f3f3f3f,id = 1;
for(int j = 1;j <= n;j ++)
{
if(v[j]) continue;
dist[j] = min(dist[j],dist[x]+G[x][j]);
if(dist[j]<mn)
{
mn = dist[j];id = j;
}
}
v[id] = true;
x = id;
}
}
void solve()
{
cin >> n >> m >> s;
init();
for(int i = 0;i < m;i ++)
{
int a,b,c;
cin >> a >> b >> c;
G[a][b] = min(G[a][b],c);//重边只存最小的
//G[b][a] = min(G[b][a],c);
}
dijkstra(s);
for(int i = 1;i <= n;i ++) cout << dist[i] << " ";
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int T = 1;//cin >> T;
while(T--)
{
solve();
}
return 0;
}
下面这个是用前向星存图的朴素版
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4+10,M=5e5+10;
int cnt,n,m,s;
int h[N],e[M],w[M],ne[M];//链式前向星
int dist[N];
bool v[N];
void add(int a,int b,int c)
{
ne[++cnt] = h[a];e[cnt] = b;w[cnt] = c;h[a] = cnt;
}
void init()
{
memset(dist,0x3f,sizeof dist);
memset(v,false,sizeof v);
}
//朴素版dijkstra
void dijkstra(int x)
{
dist[x] = 0;
v[x] = true;
for(int i = 1;i <= n;i ++)
{
int mn = 0x3f3f3f3f,id = 1;
for(int j = h[x];j!=0;j = ne[j])
{
int edg = e[j];
if(v[edg]) continue;
dist[edg] = min(dist[edg],dist[x]+w[j]);
if(dist[edg]<mn)
{
mn = dist[edg];id = edg;
}
}
v[id] = true;
x = id;
// for(int i = 1;i <= n;i ++) cout << dist[i] << " ";
// cout << "\n";
}
}
void solve()
{
cin >> n >> m >> s;
init();
for(int i = 0;i < m;i ++)
{
int a,b,c;
cin >> a >> b >> c;
add(a,b,c);
}
dijkstra(s);
for(int i = 1;i <= n;i ++) cout << dist[i] << " ";
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int T = 1;//cin >> T;
while(T--)
{
solve();
}
return 0;
}
dijkstra堆优化版
所谓堆优化就是利用小根堆的性质来优化查找最小值的那步的时间
这里的代码用来链式前向星存图,这是用来优化空间消耗的。不懂前向星的自行搜索
小根堆就是 一个父节点权值小于孩子节点的二叉树,那么根据这个,根节点就是最小值 所在。
一般小根堆用标准库的优先队列实现。
这里对定义所需参数解释一下:
第一个参数表示节点存储类型,
第二个参数表示存储类型的集合,这里就用vector,
第三个表示里边的排序规则。因为要模拟小根堆,因此 用的仿函数 greater<> ,尖括号里边也是数据类型,就第一个参数那个。表示从小到大排列。(大根堆就用less<>)。仿函数可以用标准库的,也可以自己根据你的数据类型写一个cmp函数。
这里用pair存节点信息,first 存dist ,second存节点编号。因为pair默认第一个成员进行比较
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int N = 2e5+10,M=2e5+10;
int cnt,n,m,s;
int h[N],e[M],w[M],ne[M];//链式前向星
int dist[N],v[N];
void add(int a,int b,int c)
{
ne[++cnt] = h[a];e[cnt] = b;w[cnt] = c;h[a] = cnt;
}
void init()
{
memset(dist,0x3f,sizeof dist);
memset(h,0,sizeof h);
memset(v,false,sizeof v);
}
//堆优化版dijkstra
void dijkstra(int x)
{
//priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> q;//小根堆
priority_queue<pii,vector<pii>,greater<pii>> q;
q.push({0,x});
dist[x] = 0;
int num = 0;
while(!q.empty())
{
if(num==n) break;
pii pa = q.top();
q.pop();
int id = pa.second;
if(v[id]) continue;//有重边的话同一个点会被插入队列多次,只需要最短的那次 其余直接跳过
v[id] = true;
for(int j = h[id];j != 0;j = ne[j])
{
int eg = e[j];
if(v[eg]) continue;
dist[eg] = min(dist[eg],dist[id]+w[j]);
q.push({dist[eg],eg});
}
num ++;
}
}
void solve()
{
cin >> n >> m;
init();
for(int i = 0;i < m;i ++)
{
int a,b,c;
cin >> a >> b >> c;
add(a,b,c);
}
s = 1;
dijkstra(s);
cout << dist[n];
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int T = 1;//cin >> T;
while(T--)
{
solve();
}
return 0;
}
bellman-ford
有点像对于边操作的dij,(dij是对点的),存储边集,每次遍历边集,对边两端进行松弛操作,执行n次
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+10,M = 2e5+10;
int n,m,s;
struct node{
int a,b,w;
}edge[M];
int dist[N];//backup[N];
//思想比较简单 就是每次对每条边看看两端点能不能进行松弛操作
void bellmanford(int x)
{
memset(dist,0x3f,sizeof dist);
dist[x] = 0;
for(int i = 1;i <= n;i ++)
{
//memcpy(backup,dist,sizeof dist);
for(int j = 0;j < m;j ++)
{
int a = edge[j].a,b = edge[j].b,c = edge[j].w;
dist[b] = min(dist[b],dist[a]+c);//有的会backup但是目前我没想通为啥,好像不用也行的通
// cout << a << "--" << b <<":"<< dist[b] << " ";
}
// cout << "\n";
}
}
void solve()
{
cin >> n >> m >> s;
for(int i = 0;i < m;i ++)
{
int a,b,c;cin >> a >> b >> c;
edge[i].a = a;
edge[i].b = b;
edge[i].w = c;
}
bellmanford(s);
for(int i = 1; i <= n;i ++) cout << dist[i] << " ";
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int T = 1;//cin >> T;
while(T--)
{
solve();
}
return 0;
}
spfa
用队列对bellman进行优化的思想。bellman本质是对于一个点,对它能到达的点进行松弛,那么这里我们就只遍历能访问的点的边,用队列存起来。
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+10,M=2e5+10;
int h[N],e[M],w[M],ne[M];
int n,m,cnt,s;
int dist[N];
bool v[N];
//容易被卡成 bellman-ford
void add(int a,int b,int c)
{
ne[++cnt] = h[a];e[cnt] = b;w[cnt] = c;h[a] = cnt;
}
void spfa(int x)
{
memset(dist,0x3f,sizeof dist);
dist[x] = 0;
queue<int> q;
q.push(x);
while(!q.empty())
{
int p = q.front();
v[p] = false;
q.pop();
for(int i = h[p];i!=0;i = ne[i])
{
int j = e[i];
if(dist[j]>dist[p]+w[i])
{
dist[j] = dist[p] +w[i];
if(!v[j])
{
q.push(j);
v[j] = true;
}
}
}
}
}
void solve()
{
cin >> n >> m >> s;
for(int i= 0;i < m;i ++)
{
int a,b,c;
cin >> a >> b >> c;
add(a,b,c);
}
spfa(s);
for(int i = 1; i <= n;i ++) cout << dist[i] << " ";
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int T = 1;//cin >> T;
while(T--)
{
solve();
}
}
floyd
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
int n,m;
int dist[N][N];
//代码简单,动态规划思想
void sovle()
{
cin >> n >> m;
memset(dist,0x3f,sizeof dist);
for(int i = 1; i <= n;i ++) dist[i][i] = 0;
for(int i = 9;i < m;i ++)
{
int a,b,c;
cin >> a >> b >> c;
dist[a][b] = c;
}
//对于k放外边是因为动态规划需要利用已有的结果,而k放最里边循环会导致 i,j使用未被更新的i,k和k,j;出现错误
for(int k = 1;k <= n;k ++)
for(int i = 1;i <= n;i ++)
for(int j = 1;j <= n;j ++)
dist[i][j] = min(dist[i][j],dist[i][k]+dist[k][j]);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int T = 1;//cin >> T;
while(T--)
{
solve();
}
return 0;
}
后边写的有点草率,详细的后面再补