目录
拓补排序的核心找入度为0的点,然后擦除跟该点相连的边。直到所有点都被找过(假设此时所有点都是入度为0的点,即有向无环图)或者入度为0的点没了(假设此时是有向有环图)
拓补排序大多用来看有向图有没有环
核心代码思路:
初始化:入度为0的点丢入队列。
1.把队头元素取出,加入答案
2.删除跟该元素相连的边
3.判断删除前跟该元素相连的元素,在删除了边后,入度是否为0,为0加入队列。
1.课程表1
思路就是有向图是否有环
class Solution { public: bool canFinish(int numCourses, vector<vector<int>>& prerequisites) { unordered_map<int, vector<int>> graph; int m = prerequisites.size(); if(m==0)return true; unordered_map<int, int> input; queue<int> mp; for (int i = 0; i < m; i++) { input[prerequisites[i][0]]++; graph[prerequisites[i][1]].push_back(prerequisites[i][0]); } for (int i = 0; i < numCourses; i++) { if (input[i] == 0) { mp.push(i); input[i]=-1; } } int cnt = 0; while (mp.size()) { int tmp = mp.front(); cout<<tmp<<endl; mp.pop(); cnt++; for (auto& e : graph[tmp]) { input[e]--; if(input[e]==0)mp.push(e); } } if (cnt == numCourses) return true; return false; } };
2.课程表2
思路没有太大变化,记得加进去,最后判断下即可
class Solution { public: vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) { vector<int>ret(0); unordered_map<int,int>input; queue<int>mp; unordered_map<int,vector<int>>graph; for(auto &e:prerequisites) { input[e[0]]++; graph[e[1]].push_back(e[0]); } for(int i=0;i<numCourses;i++) { if(input[i]==0) { mp.push(i); } } while(mp.size()) { int tmp=mp.front(); ret.push_back(tmp); mp.pop(); for(auto &e:graph[tmp]){ input[e]--; if(input[e]==0)mp.push(e); } } if(ret.size()!=numCourses)return {}; return ret; } };
3.火星词典
非常恶心,思路是把先把各个字符的大小关系,用图的形式存储,然后转化为拓补排序的问题。
写起来细节很多,我的写法又比较粗糙,提交了7、8次,按照反馈改代码,最后终于过了,但我个人觉得不太行,这样写不适合其他赛制,所以我试着简化我的代码,让我代码更加清晰,先把第一次过的放上来
class Solution { public: string alienOrder(vector<string>& words) { unordered_map<char,unordered_set<char>>graph; int n=words.size(); unordered_map<char,int>input; unordered_set<char>num; if(n==1)return words[0]; for(int i=0;i<n;i++) { for(int j=i+1;j<n;j++) { int left=0,right=0,na=words[i].size(),nb=words[j].size(); while(left<na&&right<nb&&words[i][left]==words[j][right]){ num.insert(words[i][left]); num.insert(words[j][right]); left++,right++; } if(left<na&&right<nb) { num.insert(words[i][left]); num.insert(words[j][right]); if(graph[words[i][left]].count(words[j][right])==0)input[words[j][right]]++; graph[words[i][left]].insert(words[j][right]); if(graph[words[j][right]].count(words[i][left])!=0)return ""; } else{ if(left<na) { return ""; } } while(left<na) { num.insert(words[i][left]); left++; } while(right<nb) { num.insert(words[j][right]); right++; } } } string ret=""; queue<char>mp; for(auto&e:num) { if(input[e]==0) { mp.push(e); } } while(mp.size()) { char tmp=mp.front(); mp.pop(); ret+=tmp; for(auto &e:graph[tmp]) { input[e]--; if(input[e]==0)mp.push(e); } } return ret; } };
比较有条理的版本
class Solution { public: unordered_map<char,int>input;//统计入度 unordered_map<char,unordered_set<char>>graph;//图 bool add(string &a,string &b)//增加关系 { int n=min(a.size(),b.size());//取小的,因为如果字典中,如果前面的字符串长度>后面的字符串,说明无法还原。 //而在可以还原的情况下,取小的,以免下标越界即可,至于不合法判断在最后 int i=0; for(;i<n;i++) { if(a[i]!=b[i]) { if(!graph.count(a[i])||!graph[a[i]].count(b[i])) { graph[a[i]].insert(b[i]); input[b[i]]++; } break;//在第一个不同字母处,如果 s 中的字母在这门外星语言的字母顺序中位于 t 中字母之前,那么 s 的字典顺序小于 t } } if(i==b.size()&&i<a.size())return false; //不合法,原因在上面有写 return true; //如果前面 min(s.length, t.length) 字母都相同,那么 s.length < t.length 时,s 的字典顺序也小于 t } string alienOrder(vector<string>& words) { for(auto &e:words) { for(auto &a:e) { input[a]=0; } }//初始化,因为这题有可能出现,有字符虽然在字典里,但在比较过程中,因为巧合,没有被加入input int n=words.size(); for(int i=0;i<n;i++) { for(int j=i+1;j<n;j++) { if(!add(words[i],words[j]))return ""; } } //拓补排序 queue<char>mp; for(auto&e:input){ if(e.second==0)mp.push(e.first); } string ret; while(mp.size()) { char tmp=mp.front(); mp.pop(); ret+=tmp; for(auto &e:graph[tmp]) { input[e]--; if(input[e]==0)mp.push(e); } } for(auto&e:input) { if(e.second!=0)return ""; } return ret; } };