C++实现拓扑排序

问题描述:
拓扑排序指的是:输入一张有向图,如果点 X X X 到点 Y Y Y 存在一条或多条有向边,表示点 Y Y Y 必须在点 X X X 之后输出到结果序列中。

例如,在一张有3个节点的有向图中,存在着 3 → 1 3 \to 1 31 2 → 3 2 \to 3 23 2 → 1 2 \to 1 21 共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 
#include 
#include 
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 
#include 
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;
        }
    }
}

你可能感兴趣的:(算法编程题,c++,图论,数据结构)