文章目录
介绍
拓扑排序是将有向无环图G的所有顶点排成一个线性序列,使得对图G的任意两个顶点u,v如果存在u->v,那么在序列中u一定在v前面。这个序列又被称为拓扑排序。
譬如在实际问题中有这样的例子:
我们应该先学习c语言程序设计课程再去学数据结构课程。那么c语言程序设计即为数据结构的先导课程。对于一门课来说,必须要先学习它的先导课程才能很好地学习这门课,而且先导课程之间不能够形成环。
一、步骤详解
通过举得例子我们可以知道,如果某一门课程没有先导课程或是所有先导课程都已经学习完毕,那么这门课就可以学习了。如果有多门这样的课,它们的学习顺序任意。
其中抽象步骤如下:
- 定义一个队列Q,并把所有入度为0的结点加入队列
- 取队首结点,输出。然后删去所有从它出发的边,并令这些边到达的顶点入度减1,如果某个顶点的入度减为0,则将其加入队列。
- 反复进行2操作,直到队列为空。如果队列为空时入过队的节点数目恰好为N,说明拓扑排序成功,图G为有向无环图;否则,拓扑排序失败,图G中有环。
代码实现
可使用邻接表实现拓扑排序。显然,由于需要记录结点的入度,因此需要额外建立一个数组inDegree[MAXV],并在程序一开始读入图时就记录好每个结点的入度。接下来就只需要按上面所说的步骤实现即可,其伪代码如下:
vector<int> G[MAXV];//邻接表
int n,m,inDegree[MAXV];//顶点数,入度
bool topologicalSort(){
int num=0;//记录加入拓扑序列的顶点数
queue<int> q;
for(int i=0;i<n;i++){
if(inDegree[i]==0){
q.push(i);//将所有入度为0的顶点入队
}
}
while(!q.empty()){
int u=q.front();//取队首顶点u
q.pop();
for(int i=0;i<G[u].size();i++){
int v=G[u][i]//u的后继节点v
inDrgree[v]--;//顶点v的入度减1
if(inDegree[v]==0){//顶点v的入度减为0则入队
q.push(v);
}
}
G[u].clear();//清空顶点u的所有出边(如无必要可不写)
num++;//(拓扑排序的顶点数加1)
}
if(num==n)return true;//加入拓扑序列的顶点数为n,说明拓扑排序成功
else return false; //加入拓扑排序的顶点数小于n,说明拓扑排序失败
}
二、实例应用
1.Leetcode:210.课程表||
现在你总共有 numCourses 门课需要选,记为 0 到 numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] = [ai, bi] ,表示在选修课程 ai 前 必须 先选修 bi 。
例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示:[0,1] 。
返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组 。
2.思路
典型的拓扑排序问题,可套用上面模板,只不过需要将判断成功与否改成存储顺序的数组。
class Solution {
public:
vector<int> res;//答案顺序数组
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
vector<vector<int>> g(numCourses);//构建邻接表
vector<int> indegree(numCourses);//入度数组
for(const auto& it:prerequisites){
g[it[1]].push_back(it[0]);//构建邻接表it[0]即为it[1]的前置条件
indegree[it[0]]++;//it[0]入度+1
}
queue<int> q;
for(int i=0;i<numCourses;i++){
if(indegree[i]==0)q.push(i);//将入度为0的节点入队
}
while(!q.empty()){
int temp=q.front();//取队首顶点temp
q.pop();
res.push_back(temp);
for(const int& it:g[temp]){
indegree[it]--;//删除it的前驱节点并让入度减一
if(indegree[it]==0)q.push(it);//如果入度为0继续入队
}
}
if(res.size()==numCourses)return res;//拓扑序列的顶点数为numCourses,说明拓扑排序成功
else return {};//顶点数小于numCourses,说明存在环拓扑排序失败
}
};