下面给出我实现的代码,说来也惭愧,冷落了拓扑排序了,居然在实现完成后也仅仅认为是普通的DFS加递归问题,没有发现是拓扑排序大哥。所以特地为大哥写一篇文章。
class Solution {
public:
vector<int> cf;//保存的是节点的状态,默认为-1,表示未开始搜索;0表示正在搜索;1表示搜索完成
vector<int> seri;//保存的是结果,为了代码书写方便,也在了类中
unordered_map<int,set<int>> mpre;//保存的是以key为起点的所有其他节点,也就是边,set的大小也就是key这个节点的入度
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
cf.resize(numCourses,-1);//初始化cf
for(auto&req:prerequisites) mpre[req[0]].insert(req[1]);//初始化mpre
//为了后续访问方便
for(int i=0;i<numCourses;i++){
if(cf[i]==1) continue;//之前已经处理过了,要选择一个没搜索过的进行搜索
if(!yesWecan(i)) return {};//如果返回值为false,其实也就意味着找到了图中的一个环,因此拓扑排序失败,返回空数组
}
return seri;//所有节点都找到了,正常返回结果
}
bool yesWecan(int pre){
if(cf[pre]!=-1) return cf[pre]==1;
//cf[pre]!=-1表示要么正在探索中,此时又发现,则出现环,返回false;要么发现已经搜索完成的节点,返回true
if(mpre.count(pre)==0) {cf[pre]=1;seri.push_back(pre);return true;}
//mpre.count(pre)==0代表该节点的入度为0,可以直接加到已排序队列seri中,同时标记cf为1,返回true
cf[pre] = 0;//表示该节点正在搜索中,如果在DFS的过程中再遇到,则出现环
bool flag = true;
//下面这一部分:一个节点只有它前面的所有节点都搜索完成时,它才会被加入到排序队列中,其实也可以递减其入度
//当一个节点的入度为0时,就可以选择该节点了
for(auto&item:mpre[pre]) flag &= yesWecan(item);
if(flag) cf[pre] = 1;
seri.push_back(pre);
return flag;
}
};
每一条语句都有注释,相信自己这次能长记性了。
不过这和传统思路下得拓扑排序稍微有点区别,算是逆着来的,下面是从网上找的一张图,很直观
以上面这张图为例,正常思维是:
1、先统计各个顶点的入度;
degree[1]=0,degree[2]=1,degree[3]=2,degree[4]=2,degree[5]=2
2、将所有入度为0的顶点入队并输出;
queue = {1},输出1
3、从队列中取出一个顶点
顶点1被取出,同时删掉该顶点和所有以它为起点的有向边;表现在代码中就是:将所有v指向的顶点的入度减1,并将入度减 为0的顶点入队列。
重复上述过程直至队列为空或出现环。
因为之前的代码中有注释,所以简单说一下,如果以入度的角度考虑,则是随机找一个点V,如果V点入度为0则直接输出,如果入度不为0,则V点进入待定状态,递归找指向V节点的所有前驱节点,前驱节点如果顺利输出,则V点入度减一,当减为0时就可以输出,如果在递归过程中又出现V点,则代表环出现,拓扑失败。
下面是拓扑排序解决问题的又一案例,力扣6163题,给定条件下构造矩阵
下面是按照入度思路写下的拓扑排序源代码
class Solution {
public:
vector<vector<int>> buildMatrix(int k, vector<vector<int>>& rowConditions, vector<vector<int>>& colConditions) {
int n = rowConditions.size(), m = colConditions.size();
vector<int> rows(k, -1);
bool flag = getSortedIndex(rowConditions,rows,k);
if(!flag) return {};
vector<int> cols(k, -1);
flag = getSortedIndex(colConditions,cols,k);
if(!flag) return {};
vector<vector<int>> ret(k, vector<int>(k, 0));
for(int i=0;i<k;i++){
ret[rows[i]][cols[i]] = i+1;
}
return ret;
}
bool getSortedIndex(vector<vector<int>>& Conditions,vector<int>& Ind,int k){
vector<int> degree(k, 0);//一开始入度皆为0
set<vector<int>> uniqueConditions(Conditions.begin(),Conditions.end());//条件有些重复,去重
unordered_map<int,set<int>> edges;//记录有向边
for(auto&cond:uniqueConditions){
edges[cond[0]-1].insert(cond[1]-1);//题目的条件中下标以1开始,所以要减一
degree[cond[1]-1]++;//同上
}
queue<int> q;
for(int i=0;i<k;i++){
if(degree[i]==0) q.push(i);//将入度为0的点入队列
}
int index = 0;
while(!q.empty()){
int p = q.front();q.pop();
Ind[p] = index++;//记录排序后的下标,根据实际情况可以直接压倒vector中,作为排序后输出
for(auto&e:edges[p]){
degree[e]--;
if(degree[e]==0) q.push(e);//将入度为0的点入队列
}
}
for(int i=0;i<k;i++){
if(degree[i]!=0) return false;//成环
}
for(int i=0;i<k;i++){
if(Ind[i]==-1) Ind[i] = index++;//题目约束不一定够,剩下的随便选
}
return true;
}
};
上面的代码中有一个去重的部分,如果改set为vector可以省去去重的工作量
class Solution {
public:
vector<vector<int>> buildMatrix(int k, vector<vector<int>>& rowConditions, vector<vector<int>>& colConditions) {
int n = rowConditions.size(), m = colConditions.size();
vector<int> rows(k, -1);
bool flag = getSortedIndex(rowConditions,rows,k);
if(!flag) return {};
vector<int> cols(k, -1);
flag = getSortedIndex(colConditions,cols,k);
if(!flag) return {};
vector<vector<int>> ret(k, vector<int>(k, 0));
for(int i=0;i<k;i++){
ret[rows[i]][cols[i]] = i+1;
}
return ret;
}
bool getSortedIndex(vector<vector<int>>& Conditions,vector<int>& Ind,int k){
vector<int> degree(k, 0);//一开始入度皆为0
unordered_map<int,vector<int>> edges;//**更改地方**
for(auto&cond:Conditions){
edges[cond[0]-1].push_back(cond[1]-1);
degree[cond[1]-1]++;
}
queue<int> q;
for(int i=0;i<k;i++){
if(degree[i]==0) q.push(i);
}
int index = 0;
while(!q.empty()){
int p = q.front();q.pop();
Ind[p] = index++;
for(auto&e:edges[p]){
degree[e]--;
if(degree[e]==0) q.push(e);
}
}
for(int i=0;i<k;i++){
if(degree[i]!=0) return false;//成环
}
for(int i=0;i<k;i++){
if(Ind[i]==-1) Ind[i] = index++;//题目约束不一定够,剩下的随便选
}
return true;
}
};