拓补排序是图论的重要基础之一。其原理是寻找有向图中入度为0的节点,找到后把它输出或存储,然后把它和它的边删掉(这时又会形成新的入度为0的节点,那么再重复以上操作直到把所有的点都输出),从而形成一个序列便于遍历等操作。当然,对于同一个图,拓补排序得到的序列可能不是唯一的。
如,对于一个有向图的节点关系:
1 —> 2
1 —> 3
1 —> 5
3 —> 2
3 —> 4
4 —> 2
我们便可以得到以下序列:
1 3 4 2 5
1 3 5 4 2
…
模版代码:
已知一个有向无环图有n个节点,m条边,求一个拓补序列。
#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
vector<int> a[120];//用动态数组记录点与点之间的边
int many[120];//记录每个点的入度
int n, m;
void work(int t) {
for(int i = 0; i < a[t].size(); i++) {
many[a[t][i]]--; //删去入度为0的点的同时也删除边,则原来与之相连的点的入度减一
}
}
int main() {
memset(many, 0, sizeof(many));
scanf("%d%d", &n, &m);
for(int i = 0; i < m; i++) {
int x, y;
scanf("%d%d", &x, &y);
a[x].push_back(y);
many[y]++;
}
for(int i = n; i >= 1; i--) { //有n个点,为保证次序就求n次
for(int j = 1; j <= n; j++) { //找入度为0的节点
if(many[j] == 0) {
printf("%d", j);
work(j);
many[j] = -1;
}
}
}
return 0;
}
这段代码能用来求从小到大排列的拓补序列(是在保证访问顺序的前提下)。
一道例题:
题目见codevs 2833
http://www.codevs.cn/problem/2833/
经分析得,此题的关键是求最长的拓补序列,然后再用总点数减之即可。
将模版稍作修改可得:
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
vector<int> a[10002];
int many[10002];
int n, m;
int count = 0;
void tb(int t) {
for(int i = 0; i < a[t].size(); i++) {
many[a[t][i]]--;
}
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 0; i < m; i++){
int x, y;
scanf("%d%d", &x, &y);
a[x].push_back(y);
many[y]++;
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++) {
if(many[j] == 0) {
tb(j);
count++;
many[j] = -1;
}
}
}
if(count == n) {
printf("o(∩_∩)o");
}
else {
printf("T_T\n%d", n - count);
}
return 0;
}