dij
//二叉堆优化过的dij
#include <cmath>
#include <queue>
#include <string>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define forn(i, n) for (int i = 0; i < (n); i++)
#define forab(i, a, b) for (int i = (a); i <= (b); i++)
#define forba(i, b, a) for (int i = (b); i >= (a); i--)
#define mset(a, n) memset(a, n, sizeof(a))
#define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define P pair<int,int>
#define fi first
#define se second
using namespace std;
#define N 1000010
#define maxn 1005
#define inf 0x3f3f3f3f
#define ll long long
int head[N], ver[N], edge[N], Next[N], d[N];
int n, m, tot;
bool vis[N];
priority_queue<pair<int,int> > q; //first存的d[i]的相反数,这样就变成了小根堆
void addedge(int x,int y,int z)
{
ver[++tot] = y;
edge[tot] = z;
Next[tot] = head[x];
head[x] = tot;
}
void Init()
{
mset(head,0);
tot=0;
}
void dij()
{
mset(d,0x3f);
mset(vis,0);
d[1] = 0; //以1 为起始点,
q.push(make_pair(0, 1));
while(q.size())
{
int x = q.top().se;
q.pop();
if(vis[x])
continue;
vis[x] = 1;
for (int i = head[x]; i;i=Next[i])
{
int y = ver[i];
int z = edge[i];
if(d[y]>d[x]+z)
{
d[y] = d[x] + z; //更新
q.push(make_pair(-d[y], y));
}
}
}
}
int main()
{
fast;
cin >> n >> m;
forab(i,1,m)
{
int x, y, z;
cin >> x >> y >> z;
addedge(x, y, z);
//addedge(y,x,z); //无向边
}
dij();
forab(i, 1, n) cout << d[i] << endl; //单源最短路
system("pause");
}
SPFA
#include <cmath>
#include <queue>
#include <string>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define forn(i, n) for (int i = 0; i < (n); i++)
#define forab(i, a, b) for (int i = (a); i <= (b); i++)
#define forba(i, b, a) for (int i = (b); i >= (a); i--)
#define mset(a, n) memset(a, n, sizeof(a))
#define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define P pair<int,int>
#define fi first
#define se second
using namespace std;
#define N 1000010
#define maxn 1005
#define inf 0x3f3f3f3f
#define ll long long
int head[N], ver[N], edge[N], Next[N], d[N];
int n, m, tot;
bool vis[N];
int in[N]; //判负环可用
void addedge(int x,int y,int z)
{
ver[++tot] = y;
edge[tot] = z;
Next[tot] = head[x];
head[x] = tot;
}
void Init()
{
mset(head,0);
tot=0;
}
void spfa()
{
mset(d, 0x3f);
mset(vis, 0); //这里的vis跟dij的有所不同,spfa的vis是记录点是否在队列里面,所以要每次进出更新
d[1] = 0;
vis[1] = 1;
q.push(1);
while(q.size())
{
int x = q.front();
q.pop();
vis[x] = 0; //x出队列
for (int i = head[x]; i;i=Next[i])
{
int y = ver[i];
int z = edge[i];
if(d[y]>d[x]+z)
{
d[y] = d[x] + z;
if(!vis[y])
{
vis[y] = 1;
q.push(y);
}
}
}
}
}
int main()
{
fast;
cin >> n >> m;
forab(i,1,m)
{
int x, y, z;
cin >> x >> y >> z;
addedge(x, y, z);
//addedge(y, x, z); 无向边
}
spfa();
forab(i, 1, n) cout << d[i] << endl;
}
负环和差分约束
之前写程序设计作业的时候就了解过spfa和差分约束,这里再解释一下
负环: 一个边权和为复数的环称为负环。
不难知道,如果图中存在负环,那么无论经过多少次迭代,总存在有向边(x,y,z)使得
dist[y] > dist[x] + z则spfa算法无法结束。
根据抽屉原理(不懂自行百度,我记着小学奥数就讲过= =),若存在一个dist[x] 从起点1到节点x的最短路包含>=n条边,则这条路径必然重复经过的某个结点p,也就是说这条最短路上存在一个环,环上的每个点都能更新下一个点的dist,p绕环一圈最后可以更新他自己,因此,整个环的总长度为负数,每绕一圈总长度就会减少一点,越来越少,不可能收敛到每条边都满足三角不等式的状态。
因此 有以下方法判定法则(有了spfa就不用bellman-ford了吧,我是只看了spfa)
第一种 按照上段文章所写,设cnt[x] 表示从1到x的最短路径包含的边数,cnt[1] = 0,当执行dist[y] = dist[x] + z 时,同样更新cnt[y] = cnt[x] + 1,此时若有cnt[y] >=n 则存在负环,若正常结束算法,则没有负环
第二种 记录每个点的入队次数 次数达到n时则有负环,不过一般不如第一种的效率高。
差分约束 另一篇文章里面给过一些详细的证明这里就不写了。