拓扑排序
预备知识
一个较大的工程往往被划分成许多子工程,我们把这些子工程称作活动(activity)。在整个工程中,有些子工程(活动)必须在其它有关子工程完成之后才能开始,也就是说,一个子工程的开始是以它的所有前序子工程的结束为先决条件的,但有些子工程没有先决条件,可以安排在任何时间开始。为了形象地反映出整个工程中各个子工程(活动)之间的先后关系,可用一个有向图来表示,图中的顶点代表活动(子工程),图中的有向边代表活动的先后关系,即有向边的起点的活动是终点活动的前序活动,只有当起点活动完成之后,其终点活动才能进行。通常,我们把这种顶点表示活动、边表示活动间先后关系的有向图称做顶点活动网(Activity On Vertex network),简称AOV网。
一个AOV网应该是一个有向无环图,即不应该带有回路,因为若带有回路,则回路上的所有活动都无法进行。这种情况若在程序中出现,则称为死锁或死循环,是必须避免的。
在AOV网中,若不存在回路,则所有活动可排列成一个线性序列,使得每个活动的所有前驱活动都排在该活动的前面,我们把此序列叫做拓扑序列(Topological order),由AOV网构造拓扑序列的过程叫做拓扑排序(Topological sort)。AOV网的拓扑序列不是唯一的,满足上述定义的任一线性序列都称作它的拓扑序列。
由AOV网构造出拓扑序列的实际意义是:如果按照拓扑序列中的顶点次序,在开始每一项活动时,能够保证它的所有前驱活动都已完成,从而使整个工程顺序进行,不会出现冲突的情况。
定义
对一个有向无环图(Directed Acyclic Graph简称DAG) 进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。
#641. 拓扑排序
小数在前大数在后——维护一个入度为0的小顶堆顶点集合
#include <iostream>#include <cstdio>#include <queue>#include <vector>using namespace std;#define N 2000#define M 400000int head[N + 5], edg[M + 5], nxt[M + 5], tot;int in_degree[N + 5];void add(int s, int e) {++in_degree[e];++tot;edg[tot] = e;nxt[tot] = head[s];head[s] = tot;return ;}priority_queue<int, vector<int>, greater<int> > que;int main() {int n, m;cin >> n >> m;for (int i = 1, s, e; i <= m; ++i) {cin >> s >> e;add(s, e);}for (int i = 1; i <= n; ++i) {if (in_degree[i] == 0) que.push(i);}while (!que.empty()) {int t = que.top();que.pop();cout << t;for (int i = head[t]; i; i = nxt[i]) {--in_degree[edg[i]];if (in_degree[edg[i]] == 0) que.push(edg[i]);}if (!que.empty()) cout << " ";}cout << endl;return 0;}
#640. 食物链计数
初始入度为0点贡献为1,每个顶点的贡献为其所有前驱点贡献的和答案累加所有出度为0的点的贡献(食物链终端)(无后继的点)拓扑排序算法会遍历所有边所有点!!!
#include <iostream>#include <cstdio>#include <stack>using namespace std;const int N = 5e3;const int M = 5e5;const int P = 1e8 + 7;int head[N + 5], edg[M + 5], nxt[M + 5], tot;int in_degree[N + 5], sum[N + 5];void add(int s, int e) {++in_degree[e];++tot;edg[tot] = e;nxt[tot] = head[s];head[s] = tot;return ;}int main() {int n, m, ans = 0;scanf("%d%d", &n, &m);for (int i = 1, s, e; i <= m; ++i) {scanf("%d%d", &s, &e);add(s, e);}stack<int> s;for (int i = 1; i <= n; ++i) {if (in_degree[i]) continue;sum[i] = 1;s.push(i);}while (!s.empty()) {int t = s.top();s.pop();if (!head[t]) {ans = (ans + sum[t]) % P;continue;}for (int i = head[t]; i; i = nxt[i]) {--in_degree[edg[i]];sum[edg[i]] = (sum[edg[i]] + sum[t]) % P;if (in_degree[edg[i]] == 0) {s.push(edg[i]);}}}printf("%d\n", ans);return 0;}
#636. 旅行计划
ans[i] : 以i点作为终点,最多可以走多少城市初始时,入度为0的点ans为1, 之后其它点的ans为其所有前驱节点最大的ans + 1一旦某点的入度为0时,其ans为最终答案,因为所有前驱节点均已遍历
#include <iostream>#include <cstdio>#include <stack>using namespace std;const int N = 1e5;const int M = 2e5;int head[N + 5], edg[M + 5], nxt[M + 5], tot;int in_degree[N + 5], ans[N + 5];void add(int s, int e) {++in_degree[e];++tot;edg[tot] = e;nxt[tot] = head[s];head[s] = tot;return ;}int main() {int n, m;scanf("%d%d", &n, &m);for (int i = 1, s, e; i <= m; ++i) {scanf("%d%d", &s, &e);add(s, e);}stack<int> s;for (int i = 1; i <= n; ++i) {if (in_degree[i]) continue;ans[i] = 1;s.push(i);}while (!s.empty()) {int t = s.top();s.pop();for (int i = head[t]; i; i = nxt[i]) {--in_degree[edg[i]];ans[edg[i]] = max(ans[edg[i]], ans[t] + 1);if (in_degree[edg[i]] == 0) {s.push(edg[i]);}}}for (int i = 1; i <= n; ++i) {printf("%d\n", ans[i]);}return 0;}
#637. 排序
每插入一条边就进行一次拓扑排序,注意每次都要拷贝出一份临时的入度数组并且每插入一组点要统计此时图中的点数cnt,并且初始状态所有点的入度应为-1封装一个拓扑排序函数,返回值为拓扑序是否唯一(如果度为0的点始终为1个,则序列唯一)输出优先级:1、如果存在环 (拓扑序列长度 < cnt)2、如果可以确定序列 (n <= cnt && 序列唯一)3、最后一条边插入后也没有满足前两个条件 (最终无法确定唯一序列)
#include <iostream>#include <cstdio>#include <vector>#include <cstring>#include <stack>using namespace std;const int N = 128;const int M = 1e4;int head[N + 5], edg[M + 5], nxt[M + 5], tot;int flag[N + 5], cnt, __in_degree[N + 5];void add(int s, int e) {if (flag[s] == 0) flag[s] = 1, ++cnt;if (flag[e] == 0) flag[e] = 1, ++cnt;if (__in_degree[s] == -1) __in_degree[s] = 0;if (__in_degree[e] == -1) __in_degree[e] = 0;++__in_degree[e];++tot;edg[tot] = e;nxt[tot] = head[s];head[s] = tot;return ;}string topo_str;bool topo_sort() {vector<int> in_degree(__in_degree, __in_degree + N + 5);bool only = true;topo_str = "";stack<int> s;for (char i = 'A'; i <= 'Z'; ++i) {if (in_degree[i] == 0) s.push(i);}while (!s.empty()) {if (s.size() - 1) only = false;int t = s.top();s.pop();topo_str += ((char)t);for (int i = head[t]; i; i = nxt[i]) {--in_degree[edg[i]];if (in_degree[edg[i]] == 0) s.push(edg[i]);}}return only;}int main() {memset(__in_degree, -1, sizeof(__in_degree));int n, m;scanf("%d%d", &n, &m);char str[5];for (int i = 1; i <= m; ++i) {scanf("%s", str);add(str[0], str[2]);bool is = topo_sort();if ((int)topo_str.size() < cnt) {printf("Inconsistency found after %d relations.\n", i);return 0;}if (is && (int)topo_str.size() >= n) {printf("Sorted sequence determined after %d relations: %s.\n", i, topo_str.c_str());return 0;}}printf("Sorted sequence cannot be determined.\n");return 0;}