设有向图 G=(V,E),V 是点集, E 是边集, (x,y) 表示一条从 x 到 y 的有向边, 其边权 (或称长度) 为 w(x,y) 。设 n=∣V∣,m=∣E∣, 邻接矩阵A 是一个 n∗n 的矩阵, 我们把它定义为:
A[i,j]=⎩⎨⎧0w(i,j)+∞i=j(i,j)∈E(i,j)∈/E
邻接矩阵的空间复杂度为 O(n2) 。
我们在链表与邻接表中已经讲解过了 “邻接表”, 并用数组模拟链表的形式进行了代码实现。长度为 n 的表头数组 head 记录了从每个节点出发的第一条边在 ver 和 edge 数组中的存储位置, 长度为 m 的边集数组 ver 和 edge 记录了每条边的终点和边权, 长度为 m 的数组 next 模拟了链表指针, 表示从相同节点出发的下一条边在 ver 和 edge 数组中的存储位置。邻接表的空间复杂度为 O(n+m) 。
1 2 3 4 5 6 7 8 9 10 11
// 邻接表:加入有向边(x, y),权值为z voidadd(int x, int y, int z){ ver[++tot] = y, edge[tot] = z; // 真实数据 next[tot] = head[x], head[x] = tot; // 在表头x处插入 }
// 邻接表:访问从x出发的所有边 for (int i = head[x]; i; i = next[i]) { int y = ver[i], z = edge[i]; // 一条有向边(x, y),权值为z }
单源最短路
单源最短路径问题 (Single Source Shortest Path, SSSP 问题) 是说, 给定一张有向图 G=(V,E),V 是点集, E 是边集, ∣V∣=n,∣E∣=m, 节点以 [1,n] 之间的连续整数编号, (x,y,z) 描述一条从 x 出发, 到达 y, 长度为 z 的有向边。设 1 号点为起点, 求长度为 n 的数组 dist, 其中 dist[i] 表示从起点 1 到节点 i 的最短路径的长度。
// Dijkstra算法,O(n^2) #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> usingnamespace std; int a[3010][3010], d[3010]; bool v[3010]; int n, m;
voiddijkstra(){ memset(d, 0x3f, sizeof(d)); // dist数组 memset(v, 0, sizeof(v)); // 节点标记 d[1] = 0; for (int i = 1; i < n; i++) { // 重复进行n-1次 int x = 0; // 找到未标记节点中dist最小的 for (int j = 1; j <= n; j++) if (!v[j] && (x == 0 || d[j] < d[x])) x = j; v[x] = 1; // 用全局最小值点x更新其它节点 for (int y = 1; y <= n; y++) d[y] = min(d[y], d[x] + a[x][y]); } }
intmain(){ cin >> n >> m; // 构建邻接矩阵 memset(a, 0x3f, sizeof(a)); for (int i = 1; i <= n; i++) a[i][i] = 0; for (int i = 1; i <= m; i++) { int x, y, z; scanf("%d%d%d", &x, &y, &z); a[x][y] = min(a[x][y], z); } // 求单源最短路径 dijkstra(); for (int i = 1; i <= n; i++) printf("%d\n", d[i]); }
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<queue> usingnamespace std; constint N = 100010, M = 1000010; int head[N], ver[M], edge[M], Next[M], d[N]; bool v[N]; int n, m, tot; // 大根堆(优先队列),pair的第二维为节点编号 // pair的第一维为dist的相反数(利用相反数变成小根堆,参见0x71节) priority_queue<pair<int, int>> q;
voidadd(int x, int y, int z){ ver[++tot] = y, edge[tot] = z, Next[tot] = head[x], head[x] = tot; }
voiddijkstra(){ memset(d, 0x3f, sizeof(d)); // dist数组 memset(v, 0, sizeof(v)); // 节点标记 d[1] = 0; q.push(make_pair(0, 1)); while (q.size()) { // 取出堆顶 int x = q.top().second; q.pop(); // 第一次出队即为最短路,只有第一次出队时用来扩展 //if (x == endpoint) return; 如果只要求到终点的最短距离,直接结束 if (v[x]) continue; //已经出队过,则已经用来扩展过,则跳过 // 没有出队扩展过,现在是第一次,可以用来扩展,并标记 v[x] = 1; // 扫描所有出边 for (int i = head[x]; i; i = Next[i]) { int y = ver[i], z = edge[i]; if (d[y] > d[x] + z) { // 更新,把新的二元组插入堆 d[y] = d[x] + z; q.push(make_pair(-d[y], y)); } } } }
intmain(){ cin >> n >> m; // 构建邻接表 for (int i = 1; i <= m; i++) { int x, y, z; scanf("%d%d%d", &x, &y, &z); add(x, y, z); } // 求单源最短路径 dijkstra(); for (int i = 1; i <= n; i++) printf("%d\n", d[i]); }
int n, m, k; int h[N], e[M], ne[M], w[M], idx; int dist[N], backup[N];
voidadd(int a, int b, int c){ // 添加一条边a->b,边权为c e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ; }
/** dist[1]=0,dist[其他点]=+∞ for k 次 (迭代k次涵义:从s点经过不超过k条边的最短距离) for 所有边 a->b w dist[b]=min(dist[b],dist[a]+w) 松弛操作 **/ intbellmanford(int s, int k){ memset(dist, 0x3f, sizeof dist); dist[s] = 0; bool NegCyc; for (int i = 1; i <= k; ++i) { //经过不超过 k 条边 NegCyc = false; // 在进行新一轮循环前,一定要备份当前 dist 距离 // 避免利用到,在当前循环中被扩展更新后的点数据,进行扩展 memcpy(backup, dist, sizeof dist);//备份!!! for (int u = 1; u <= n; ++u) { for (int j = h[u]; ~j; j = ne[j]) { int v = e[j], we = w[j]; if (dist[v] > backup[u] + we) { dist[v] = backup[u] + we; NegCyc = true; } } } if (NegCyc == false) break; // 没有可以松弛的边时就停止算法 } //如果一共有 k 个点,第 k 轮循环仍然可以松弛时,说明 s 点可以抵达一个负环 if (NegCyc) cout << "存在负环" << endl; //因为有负边,dist[n] 可能比 0x3f3f3f3f 小 if (dist[n] > 0x3f3f3f3f / 2) cout << "不能到达点 n "; return dist[n]; }
intmain(){ memset(h, -1, sizeof h); cin >> n >> m >> k;
for (int i = 0; i < m; ++i) { int a, b, c; cin >> a >> b >> c; add(a, b, c); } bellmanford(1, k); if (dist[n] > 0x3f3f3f3f / 2) cout << "impossible"; else cout << dist[n]; }
// SPFA算法 #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<queue> usingnamespace std; constint N = 100010, M = 1000010; int head[N], ver[M], edge[M], Next[M], d[N]; int n, m, tot; queue<int> q; bool v[N];
voidadd(int x, int y, int z){ ver[++tot] = y, edge[tot] = z, Next[tot] = head[x], head[x] = tot; }
voidspfa(){ memset(d, 0x3f, sizeof(d)); // dist数组 memset(v, 0, sizeof(v)); // 是否在队列中 d[1] = 0; v[1] = 1; q.push(1); while (q.size()) { // 取出队头 int x = q.front(); q.pop(); v[x] = 0; // 扫描所有出边 for (int i = head[x]; i; i = Next[i]) { int y = ver[i], z = edge[i]; if (d[y] > d[x] + z) { // 更新,把新的二元组插入堆 /** cnt[y] = cnt[x] + 1; // 记录最短路经过的边数 if (cnt[v] >= n) return false; // 在不经过负环的情况下,最短路至多经过 n - 1 条边 // 因此如果经过了多于 n 条边,一定说明经过了负环 **/ d[y] = d[x] + z; if (!v[y]) q.push(y), v[y] = 1; //队列中已经有该点时,则只需要更新 dist 数据就行, //队列中不存在该点时,才需要将该点入队,表示该点待扩展 } } } }
intmain(){ cin >> n >> m; // 构建邻接表 for (int i = 1; i <= m; i++) { int x, y, z; scanf("%d%d%d", &x, &y, &z); add(x, y, z); } // 求单源最短路径 spfa(); for (int i = 1; i <= n; i++) printf("%d\n", d[i]); }
// 解法二:二分答案+双端队列BFS #include<bits/stdc++.h> usingnamespace std; constint MAX_N = 1005, MAX_M = 20005; int head[MAX_N], ver[MAX_M], edge[MAX_M], nxt[MAX_M], tot; int n, m, k, d[MAX_N]; deque<int> q;
// 插入一条从x到y长度z的有向边 voidadd(int x, int y, int z){ tot++; ver[tot] = y; edge[tot] = z; nxt[tot] = head[x]; // 在head[x]这条链的开头插入新点 head[x] = tot; }
boolsolve(int t){ memset(d, 0x7f, sizeof(d)); d[1] = 0; q.push_back(1); while (!q.empty()) { int x = q.front(); q.pop_front(); for (int i = head[x]; i; i = nxt[i]) { int y = ver[i], z = edge[i] > t ? 1 : 0; if (d[y] > d[x] + z) { d[y] = d[x] + z; if (z == 0) q.push_front(y); else q.push_back(y); } } } return d[n] <= k; }
intmain(){ cin >> n >> m >> k; for (int i = 1; i <= m; i++) { int x, y, z; scanf("%d%d%d", &x, &y, &z); add(x, y, z); add(y, x, z); } int L = 0, R = 1000001; while (L < R) { int mid = (L + R) >> 1; if (solve(mid)) R = mid; else L = mid + 1; } if (L == 1000001) puts("-1"); else cout << L << endl; }
#include<bits/stdc++.h> usingnamespace std; constint MAX_N = 100005, MAX_M = 1000005; int head[MAX_N], ver[MAX_M], edge[MAX_M], nxt[MAX_M], tot; int n, m, a[MAX_N], d[MAX_N]/*前缀min*/, f[MAX_N]/*后缀max*/; bool v[MAX_N]; // 点是否在队列中 queue<int> q;
voidadd(int x, int y, int z){ tot++; ver[tot] = y; edge[tot] = z; // 1表示只能正着走,-1表示只能倒着走,2表示正反都可以 nxt[tot] = head[x]; head[x] = tot; }
// 求d数组,从st出发,只有标记为z和2的边可以用 voidspfa(int* d, int st, int z){ d[st] = a[st]; q.push(st); v[st] = true; while (!q.empty()) { int x = q.front(); q.pop(); v[x] = false; for (int i = head[x]; i; i = nxt[i]) if (edge[i] == z || edge[i] == 2) { int y = ver[i]; int val = z == 1 ? min(d[x], a[y]) : max(d[x], a[y]); if (z == 1 && d[y] > val || z == -1 && d[y] < val) { d[y] = val; if (!v[y]) { q.push(y); v[y] = true; } } } } }
intmain(){ cin >> n >> m; for (int i = 1; i <= n; i++) scanf("%d", &a[i]); for (int i = 1; i <= m; i++) { int x, y, z; scanf("%d%d%d", &x, &y, &z); add(x, y, z); add(y, x, z == 1 ? -1 : z); } memset(d, 0x3f, sizeof(d)); spfa(d, 1, 1); // 从1出发求前缀min(d),只有1和2的边可以用 memset(f, 0xcf, sizeof(f)); spfa(f, n, -1); // 从n出发倒着求后缀max(d),只有-1和2的边可以用 int ans = 0; for (int i = 1; i <= n; i++) ans = max(ans, f[i] - d[i]); cout << ans << endl; }
#include<bits/stdc++.h> usingnamespace std; constint MAX_N = 25005, MAX_M = 150005; int head[MAX_N], ver[MAX_M], edge[MAX_M], mark[MAX_M], nxt[MAX_M], tot; int n, m, p, s, c[MAX_N], cnt, deg[MAX_N], d[MAX_N]; bool v[MAX_N]; queue<int> q; // 存的是块号 vector<int> a[MAX_N]; // 每个“道路”连通块中的点 priority_queue<pair<int, int>> heap;
voidadd(int x, int y, int z, int k){ // k==0双向, k==1单向 ver[++tot] = y, edge[tot] = z, mark[tot] = k; nxt[tot] = head[x], head[x] = tot; }
voiddfs(int x){ c[x] = cnt; a[cnt].push_back(x); for (int i = head[x]; i; i = nxt[i]) { if (mark[i] == 1) continue; int y = ver[i]; if (!c[y]) dfs(y); } }
voiddijkstra(int k){ // 算k这一块的最短路 for (int j = 0; j < a[k].size(); j++) { // for (auto x : a[k]) { int x = a[k][j]; // x是点号,k这块里面的所有点 heap.push(make_pair(-d[x], x)); } while (!heap.empty()) { int x = heap.top().second; heap.pop(); if (v[x]) continue; v[x] = 1; for (int i = head[x]; i; i = nxt[i]) { int y = ver[i]; if (mark[i] == 0) { // 正常的dijkstra模板 if (d[y] > d[x] + edge[i]) { d[y] = d[x] + edge[i]; heap.push(make_pair(-d[y], y)); } } else { d[y] = min(d[y], d[x] + edge[i]); if (--deg[c[y]] == 0) q.push(c[y]); // 拓扑排序的更新 } } } }
intmain(){ cin >> n >> m >> p >> s; for (int i = 1; i <= m; i++) { int x, y, z; scanf("%d%d%d", &x, &y, &z); add(x, y, z, 0); add(y, x, z, 0); } for (int i = 1; i <= p; i++) { int x, y, z; scanf("%d%d%d", &x, &y, &z); add(x, y, z, 1); } for (int i = 1; i <= n; i++) if (c[i] == 0) { cnt++; dfs(i); } // 统计每块的总入度,只看航线 for (int x = 1; x <= n; x++) for (int i = head[x]; i; i = nxt[i]) { if (mark[i] == 0) continue; // 这是一条从块c[x]到块c[ver[i]]的航线 deg[c[ver[i]]]++; } // 对块拓扑排序 for (int i = 1; i <= cnt; i++) if (!deg[i]) q.push(i); memset(d, 0x3f, sizeof(d)); d[s] = 0; while (!q.empty()) { int k = q.front(); // k是块号 q.pop(); dijkstra(k); } for (int i = 1; i <= n; i++) if (d[i] > n * 10000) puts("NO PATH"); elseprintf("%d\n", d[i]); }
// Floyd算法,(n^3) #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> usingnamespace std; int d[310][310]; int n, m;
intmain(){ cin >> n >> m; // 把d数组初始化为邻接矩阵 memset(d, 0x3f, sizeof(d)); for (int i = 1; i <= n; i++) d[i][i] = 0; for (int i = 1; i <= m; i++) { int x, y, z; scanf("%d%d%d", &x, &y, &z); d[x][y] = min(d[x][y], z); } // 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]); // 输出 for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) printf("%d ", d[i][j]); puts(""); } }
// Floyd传递闭包 #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> usingnamespace std; bool d[310][310]; int n, m;
intmain(){ cin >> n >> m; for (int i = 1; i <= n; i++) d[i][i] = 1; for (int i = 1; i <= m; i++) { int x, y; scanf("%d%d", &x, &y); d[x][y] = d[y][x] = 1; } for (int k = 1; k <= n; k++) for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) d[i][j] |= d[i][k] & d[k][j]; }
//Author:XuHt #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> usingnamespace std; constint N = 30; int n, m, d[N][N], e[N][N];
intfloyd(){ memcpy(e, d, sizeof(e)); for (int k = 0; k < n; k++) for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) { e[i][j] |= e[i][k] & e[k][j]; if (e[i][j] == e[j][i] && e[i][j] && i != j) return-1; } for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) if (e[i][j] == e[j][i] && !e[i][j] && i != j) return0; return1; }
voidSorting_It_All_Out(){ memset(d, 0, sizeof(d)); bool flag = 1; for (int i = 1; i <= m; i++) { char s[6]; scanf("%s", s); d[s[0]-'A'][s[2]-'A'] = 1; if (flag) { int now = floyd(); if (now == -1) { printf("Inconsistency found after %d relations.\n", i); flag = 0; } elseif (now == 1) { printf("Sorted sequence determined after %d relations: ", i); pair<int, char> ans[N]; for (int j = 0; j < n; j++) { ans[j].first = 0; ans[j].second = 'A' + j; } for (int j = 0; j < n; j++) for (int k = 0; k < n; k++) if (e[j][k]) ++ans[j].first; sort(ans, ans + n); for (int j = n - 1; j >= 0; j--) printf("%c", ans[j].second); puts("."); flag = 0; } } } if (flag) puts("Sorted sequence cannot be determined."); }
intmain(){ while (cin >> n >> m && n) Sorting_It_All_Out(); return0; }