拓扑排序 topsort【一】
针对有向无环图(DAG),只有有向无环图才能有拓扑排序。
一个有向无环图一定至少存在一个入度为零的点。
- 将所有入度为零的点入队
- 每次取出队列中的点
- 枚举所有出边并删掉这条边,如果
d[j]
为零表示前面的所有点都已经排好序了
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int e[N], ne[N], h[N], idx;
int n, m;
int q[N], d[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
bool topsort()
{
int tt = 0, hh = 0;
for (int i = 1; i <= n; i ++ )
{
if(!d[i]) q[tt ++] = i;
}
while (hh <= tt)
{
int t = q[hh ++];
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
d[j] --;
if(!d[j]) q[tt ++] = j;
}
}
return tt == n;
}
int main()
{
memset(h, -1, sizeof h);
scanf("%d%d", &n, &m);
for (int i = 0; i < m; i ++ )
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
d[b] ++;
}
if(topsort())
{
for (int i = 0; i < n; i ++ )
{
cout << q[i] << ' ';
}
puts("");
}else
{
puts("-1");
}
return 0;
}
最短路问题【2】
最短路问题
稀疏图类问题大多用邻接表来做,稠密图大多用邻接矩阵
- 单源最短路
- 所有边权值都为正数
- 朴素Dijkstra算法 O(n^2) → 适合稠密图
- 堆优化版的Dijkstra算法 O(mlogn)
- 存在负权边
- Bellman-Ford O(mn)
- SPFA O(m),最坏 O(mn)【容易被卡】
- 所有边权值都为正数
- 多元汇最短路 Floyd算法 O(n^3)
朴素Dijkstra算法
思路
- d[1] = 0, d[i] = INF, S为当前已经确定最短距离的点
- 找到不在S中的距离最近的点,将其加到S中去,用t来更新其他点的距离,从t到其他点的距离是否大于原来的值 d[x] > d[t] + w
- 每次循环都可以确定一个点的最短距离,循环n次后就可以确定从1号点到其他
n-1
个点的最短距离
dijstra算法不能解决负权边的原因是,dijkstra要求每个点被确定后st[j] = true
,此时d[j]
已经是最短距离,无法被更新了,而如果存在负权边的话,已经确定的d[j]
不一定是最短的。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510;
int d[N], g[N][N];
bool st[N];
int n, m;
const int INF = 0x3f3f3f3f;
int dijkstra()
{
memset(d, 0x3f, sizeof d);
d[1] = 0;
for (int i = 0; i < n; i ++ )
{
int t = -1;
for (int j = 1; j <= n; j ++ )
{
if(!st[j] && (t == -1 || d[t] > d[j]))
t = j;
}
for (int j = 1; j <= n; j ++ )
{
d[j] = min(d[j], d[t] + g[t][j]);
}
st[t] = true;
}
if(d[n] == INF) return -1;
return d[n];
}
int main()
{
scanf("%d%d", &n, &m);
memset(g, 0x3f, sizeof g);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
g[a][b] = min(g[a][b], c);
}
printf("%d\n", dijkstra());
return 0;
}
堆优化版的Dijkstra算法
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 1000010, INF = 0x3f3f3f3f;
int d[N];
int e[N], h[N], ne[N], w[N], idx;
int n, m;
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
int dijkstra()
{
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, 1});
d[1] = 0;
while (heap.size())
{
auto t = heap.top();
heap.pop();
int x = t.x, y = t.y;
if(st[y]) continue;
st[y] = true;
for (int i = h[y]; i != -1; i = ne[i])
{
int j = e[i];
if(d[j] > x + w[i])
{
d[j] = x + w[i];
heap.push({d[j], j});
}
}
}
if(d[n] == INF) return -1;
return d[n];
}
int main()
{
memset(d, 0x3f, sizeof d);
memset(h, -1, sizeof h);
scanf("%d%d", &n, &m);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
printf("%d\n", dijkstra());
return 0;
}
Bellman-Ford
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510, M = 100010, INF = 0x3f3f3f3f;
int d[N], backup[N];
struct Node{
int a, b, w;
}edges[M];
int n, m, k;
int bellman_ford()
{
memset(d, 0x3f, sizeof d);
d[1] = 0;
for (int i = 0; i < k; i ++ )
{
memcpy(backup, d, sizeof d);
for (int j = 0; j < m; j ++ )
{
int a = edges[j].a, b = edges[j].b, w = edges[j].w;
d[b] = min(d[b], backup[a] + w);
}
}
if(d[n] > INF / 2) return -1;
return d[n];
}
int main()
{
scanf("%d%d%d", &n, &m, &k);
for (int i = 0; i < m; i ++ )
{
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
edges[i] = {a, b, w};
}
int t = bellman_ford();
if(t == -1)
{
puts("impossible");
}else
{
cout << t << endl;
}
return 0;
}
SPFA
对bellman-ford算法的一个优化
对更新过的点,更新其到其他点的最短距离(只有前面的点变小了,后面的点才会更新
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int e[N], h[N], ne[N], w[N], idx;
int d[N];
bool st[N];
int n, m;
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
int spfa()
{
memset(d, 0x3f, sizeof d);
int hh = 0, tt = 0;
int q[N];
q[0] = 1;
d[1] = 0;
st[1] = true;
while (hh <= tt)
{
int t = q[hh ++];
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i] )
{
int j = e[i];
if(d[j] > d[t] + w[i])
{
d[j] = d[t] + w[i];
if(!st[j])
{
q[++ tt] = j;
st[j] = true;
}
}
}
}
if(d[n] == 0x3f3f3f3f) return -1;
return d[n];
}
int main()
{
memset(h, -1, sizeof h);
scanf("%d%d", &n, &m);
for (int i = 0; i < m; i ++ )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
int t = spfa();
if(t == -1) puts("impossible");
else cout << t << endl;
return 0;
}
Floyd算法
Floyd算法基于动态规划
是从i->j的最短路的长度
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 210, M = 200010, INF = 0x3f3f3f3f;
int d[N][N];
int n, m, Q;
void floyd()
{
for (int k = 1; k <= n; k ++ )
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
int main()
{
scanf("%d%d%d", &n, &m, &Q);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
if(i == j) d[i][j] = 0;
else d[i][j] = INF;
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
d[a][b] = min(d[a][b], c);
}
floyd();
while (Q -- )
{
int a, b;
scanf("%d%d", &a, &b);
if(d[a][b] > INF / 2) puts("impossible");
else cout << d[a][b] << endl;
}
return 0;
}