问题描述:
拓扑排序指的是:输入一张有向图,如果点
X
X
X 到点
Y
Y
Y 存在一条或多条有向边,表示点
Y
Y
Y 必须在点
X
X
X 之后输出到结果序列中。
例如,在一张有3个节点的有向图中,存在着 3 → 1 3 \to 1 3→1 、 2 → 3 2 \to 3 2→3 和 2 → 1 2 \to 1 2→1 共3条有向边,则经过拓扑排序之后的序列应该为 2 , 3 , 1 2, 3, 1 2,3,1。
思路:
很明显只有入度为0的点是可以直接添加到结果集中的。
因此,我们可以不断将入度为0的点从有向图中取出,并将其指向的点的入度减1,直到有向图中不存在入度为0的点
如果算法结束后,取出的点的数量不等于有向图总的点的数量,说明该有向图存在环,不适用于拓扑排序!
一种基于上述思路的实现,时间复杂度为O(V+E),但是不知道为什么没有AC:
//
// main.cpp
// TopologicalSorting
//
// Created by 胡昱 on 2021/12/30.
//
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
// 节点最大数量N
const int N = 300;
// 记录有向图的二维数组,graph[X][Y]代表了X号点到Y号点有一条有向边
// 注意节点编号是从1开始的
int graph[N + 1][N + 1];
// 记录每个点入度的数组
int in_degree[N + 1];
// 记录拓扑排序结果的数组及其下标
int result[N + 1];
int result_index;
// 主函数
int main(int argc, const char * argv[]) {
// 共T组测试用例
int T;
cin >> T;
while(T-- > 0) {
// 输入点的数量n和有向边的数量m
int n, m;
cin >> n >> m;
// 初始化有向图、入度数组以及边数组
memset(graph, 0, sizeof(graph));
memset(in_degree, 0, sizeof(in_degree));
// 输入有向边
bool hasRing = false;
for(int mi = 0; mi < m; ++mi) {
// X号点到Y号点有一条有向边,表示Y号点必须出现在X号点之后
int X, Y;
cin >> X >> Y;
// 如果有环肯定不能进行拓扑排序
if(X == Y) {
hasRing = true;
break;
}
// 设置有向图
graph[X][Y] = 1;
// 更新入度数组
in_degree[Y] += 1;
}
if(hasRing) {
cout << 0 << endl;
continue;
}
// 初始化结果数组
memset(result, 0, sizeof(result));
result_index = 0;
// 开始拓扑排序
// 思路为:不断将入度为0的点从有向图中取出,直到有向图中不存在入度为0的点
// 创建入度为0的点集并初始化
queue<int> in_degree_equal_0;
for(int ni = 1; ni <= n; ++ni) {
if(in_degree[ni] == 0) {
in_degree_equal_0.push(ni);
}
}
// 不断将入度为0的点从有向图中取出
while(!in_degree_equal_0.empty()) {
// 取出一个入度为0的点,并加入结果集中
int removed_point = in_degree_equal_0.front();
in_degree_equal_0.pop();
result[result_index] = removed_point;
++result_index;
// 更新有向图以及其他点的入度
for(int ni = 1; ni <= n; ++ni) {
// 删除已经取出的点到其他点的有向边并更新其入度
if(graph[removed_point][ni] == 1) {
--in_degree[ni];
// 如果该点入度为0了则加入到入度为0的点集中等待取出
if(in_degree[ni] == 0) {
in_degree_equal_0.push(ni);
}
}
}
}
// 拓扑排序结束
// 输出结果
// 如果不是所有点都可以取出,说明该有向图有环,不存在拓扑排序
if(result_index != n) {
cout << 0 << endl;
}
else {
for(int ri = 0; ri < result_index; ++ri) {
if(ri != 0) {
cout << " ";
}
cout << result[ri];
}
cout << endl;
}
}
}
一种可以AC的实现,虽然时间复杂度比较高:
//
// Answer.cpp
// TopologicalSorting
//
// Created by 胡昱 on 2021/12/30.
//
#include <iostream>
#include <cstring>
using namespace std;
// 节点最大数量N
const int N = 300;
// 点的数量n和有向边的数量m
int n, m;
// 记录有向图的二维数组,graph[X][Y]代表了X号点到Y号点有一条有向边
// 注意节点编号是从1开始的
int graph[N + 1][N + 1];
// 记录每个点是否已被移除
int is_deleted[N + 1];
// 记录拓扑排序结果的数组及其下标
int result[N + 1];
// 判断该点是否可被移除
bool canBeDeleted(int x) {
// 点不可以被重复移除
if(is_deleted[x] == 1) {
return false;
}
// 如果存在其他没有被移除的点存在有向边指向点x,则点x还不能被移除
for(int ni = 1; ni <= n; ++ni) {
if(graph[ni][x] == 1 && is_deleted[ni] == 0) {
return false;
}
}
// 如果上述条件均不成立则该点可以移除
return true;
}
// 主函数
int main(int argc, const char * argv[]) {
// 共T组测试用例
int T;
cin >> T;
while((T--) > 0) {
// 输入点的数量n和有向边的数量m
cin >> n >> m;
// 初始化有向图、是否被删除数组、结果数组
memset(graph, 0, sizeof(graph));
memset(is_deleted, 0, sizeof(is_deleted));
memset(result, 0, sizeof(result));
// 输入有向边
bool hasRing = false;
for(int mi = 0; mi < m; ++mi) {
// X号点到Y号点有一条有向边,表示Y号点必须出现在X号点之后
int X, Y;
cin >> X >> Y;
// 如果有环肯定不能进行拓扑排序
if(X == Y) {
hasRing = true;
break;
}
// 设置有向图
graph[X][Y] = 1;
}
if(hasRing) {
cout << 0 << endl;
continue;
}
// 开始拓扑排序
// temp为记录上一次结果集长度的变量
int k = 0, temp;
for(int ni = 0; ni < n; ++ni) {
// 这一步主要是用来记录是否有新的入度为0的点出现
temp = k;
// 寻找一个入度为0的点
for(int x = 1; x <= n; ++x) {
if(canBeDeleted(x)) {
// 将该点标记为已删除
is_deleted[x] = 1;
// 放入结果集中
result[k] = x;
// 结果集长度加1
++k;
break;
}
}
// 如果k等于temp,说明没有点canBeDeleted了,即已经没有入度为0的点了,则不存在拓扑排序
if(k == temp) {
cout << 0 << endl;
break;
}
}
// 拓扑排序结束
// 输出结果
if(k != temp) {
for(int ni = 0; ni < n; ++ni) {
if(ni != 0) {
cout << " ";
}
cout << result[ni];
}
cout << endl;
}
}
}