Skip to content

拓扑排序

拓扑排序理论

拓扑排序(Topological sorting)解决的问题是对有向无环图的所有的节点排序,要求任意两个存在依赖关系的节点,被依赖的节点都必须在另一个节点前面。

拓扑排序的顺序并不唯一,除了排序也可以用于判断有向图中是否存在环。

实现

通常使用Kahn算法解决,其核心在于入度为0的节点就一定没有被依赖

主要思路如下:

  • 建图,同时记录每个节点入度。

  • 遍历每个节点的入度,将入度为0的节点全部入队。

  • 取出队头,遍历它的所有出边,将出边终点的入度-1,并放进结果数组

  • 如果某个终点的入度减至0,则入队。

  • 检查是否所有节点都放入了结果数组

  • 如果全部放入,则该数组的顺序就是其中一种拓扑排序

  • 如果没有,则该图中存在环或不连通。

拓扑排序输出的序列不唯一,如果有顺序的要求,可以用优先队列替换队列

例题

拓扑排序

题目链接:210. 课程表 II - 力扣(LeetCode)

点击展开题目

现在你总共有 numCourses 门课需要选,记为 0numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] = [ai, bi] ,表示在选修课程 ai必须 先选修 bi

  • 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示:[0,1]

返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组

示例 1:

输入:numCourses = 2, prerequisites = [[1,0]]
输出:[0,1]
解释:总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。

示例 2:

输入:numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
输出:[0,2,1,3]
解释:总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。

示例 3:

输入:numCourses = 1, prerequisites = []
输出:[0]

提示:

  • 1 <= numCourses <= 2000
  • 0 <= prerequisites.length <= numCourses * (numCourses - 1)
  • prerequisites[i].length == 2
  • 0 <= ai, bi < numCourses
  • ai != bi
  • 所有[ai, bi] 互不相同

实现代码

点击展开代码
class Solution {
public:

//拓扑排序
//建图,int du[]存入度
//初始化后遍历入度,把入度为0的点全部压进队列
//问队列top的出度,存top并将所有top出度节点的入度-1,如果某个节点减到0了也压进队列,直到队列空
//统计已经问过的点,没问完就不能拓扑排序,问完了则输出

//统计:开等同点数的数组,初始化为-1,问完以后检查最后一个点是否是-1。

    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        vector<int> du(numCourses,0);
        vector<vector<int>> graph(numCourses);
        queue<int> que;
        vector<int> res(numCourses,-1);

        //建图
        for(auto point:prerequisites){
            graph[point[1]].push_back(point[0]);
            du[point[0]]+=1;
        }

        //初始化
        if(!que.empty())que.pop();
        for(int i=0;i<numCourses;i++){
            if(du[i]==0)que.push(i);
        }

        //轮询
        int idx=0;
        while(!que.empty()){
            int curr=que.front();
            res[idx++]=curr;
            que.pop();

            for(auto i:graph[curr]){
                du[i]-=1;
                if(du[i]==0)que.push(i);
            }
        }

        //输出
        if(res[numCourses-1]==-1)return vector<int>();
        else return res;
    }
};

反向建图

题目链接:码蹄集OJ-事务处理

思路

该题必须用反向建图的原因是,使用小根堆拓扑排序只能选定下一步可选节点中优先度最高的节点,如果高优先度的节点位于一条较长的事件链尾部,就难以按正确的优先顺序进行排序。

考虑反向建图,对于约束[i,j](需要先完成事件i才能进行事件j),建图时记为j->i,则每个节点的入度表示完成该节点才能进行的节点的数量

AOE网

AOE网(Activity of edge Network)是特殊的有向无环图,有且仅有一个入度为0的起点和一个出度为0的终点,需要解决对于一组存在依赖关系的事件,某个特定事件允许的最早和最晚发生时间,常应用于分模块进行的工程。