目录
- 0.原理讲解
- 1.有向无环图
- 2.AOV网
- 3.拓扑排序
- 4.实现拓扑排序
- 5.如何建图?
 
- 1.课程表
- 1.题目链接
- 2.算法原理详解
- 3.代码实现
 
- 2.课程表 II
- 1.题目链接
- 2.算法原理详解
- 3.代码实现
 
- 3.火星词典
- 1.题目链接
- 2.算法原理详解
- 3.代码实现
 
0.原理讲解
1.有向无环图
-  有向无环图:DAG -> Directed Acyclic Graph - 入度:有多少条边指向它
- 出度:有多少条从它出去
  
 
-  有向有环图 - 结点4 5 6构成回路
  
 
- 结点
2.AOV网
- 顶点活动图:AOV网 -> Activity on Vextex Network - 即:在有向无环图中,用顶点来表示一个活动,用边来表示活动的先后顺序的图结构
- 例:下图为做菜的流程图
  
 
3.拓扑排序
-  形象地说:找到做事情的先后顺序 
-  注意:拓扑排序的结果可能不是唯一的 
-  上图做菜的流程图中,可能会有下面两种排序结果 
-  如何排序? - 找出图中入度为0的点,然后输出
- 删除与该点连接的边
- 重复上述两步操作,直到图中没有点或者没有入度为0的点为止
 
-  为什么判断条件是没有点 || 没有入度为0的点? - 因为图中可能有环,是没办法到达没有点这个情况的
 
-  拓扑排序重要应用:判断有向图中是否有环 
4.实现拓扑排序
- 借助队列,来一次BFS即可
- 流程: - 初始化:把所有入度为0的点加入到队列中
- 当队列不为空的时候: - 拿出队头元素,加入到最终结果中
- 删除与该元素相连的边
- 判断:与删除边相连的点,是否入度变成0 - 如果入度为0,加入到队列中
 
 
 
5.如何建图?
- 看稠密(数据量)选择合适的存储结构 - 邻接矩阵
- 邻接表
 
- 邻接表除了用哈希桶存储外,也可以用以下容器抽象出来 - vector<vector<int>> edges- 每个vector表示每个结点,vector里面的内容是其出度表
 
- 每个
- unordered_map<int, vector<int>> edges- int -> vector<int>== 当前结点 -> 出度表
- 此处的元素类型是弹性的,此处的例子为int,若有需要string等其他类型也可以
 
- unordered_map<char, unordered_set<char>> edges
- 总结:根据具体元素,具体场景,灵活的使用容器即可
 
- 根据算法流程,灵活建图 - 每个结点的入度? - vector<int> in
- 下标为对应结点,内容为对应入度
 
- 每个结点的出度? - vector<int> out
- 下标为对应结点,内容为对应出度
 
- ……
 
- 每个结点的入度? 
1.课程表
1.题目链接
- 课程表
2.算法原理详解
- 本题问题可以转化为: - 能否拓扑排序?
- 是否是有向无环图?/ 有向图中是否有环?
 
- 本题建图实现:unordered_map<int, vector<int>> edges
3.代码实现
bool CanFinish(int n, vector<vector<int>>& prerequisites) 
{unordered_map<int, vector<int>> edges; // 邻接表vector<int> in(n); // 存储每一个结点的入度// 1.建图for(auto& e : prerequisites){int a = e[0], b = e[1]; // b -> aedges[b].push_back(a); // 构建图的逻辑结构in[a]++; // 入度表}// 2.拓扑排序BFS// (1) 把所有入度为0的结点加入队列queue<int> q;for(int i = 0; i < n; i++){if(in[i] == 0){q.push(i);}}// (2) BFSwhile(q.size()){int tmp = q.front();q.pop();// 修改相连结点的边for(auto& e : edges[tmp]){in[e]--;if(in[e] == 0){q.push(e);}}}// 3.判断是否有环for(auto& e : in){if(e){return false;}}return true;
}
2.课程表 II
1.题目链接
- 课程表 II
2.算法原理详解
- 本题问题可以转化为: - 能否拓扑排序?
- 是否是有向无环图?/ 有向图中是否有环?
 
- 本题建图实现:vector<vector<int>> edges
3.代码实现
vector<int> findOrder(int n, vector<vector<int>>& prerequisites) 
{vector<vector<int>> edges(n);vector<int> in(n);// 1.建图for(auto& v : prerequisites){int a = v[0], b = v[1]; // b -> aedges[b].push_back(a);in[a]++;}// 2.拓扑排序vector<int> ret;queue<int> q;// (1) 将所有入度为0的点入队列for(int i = 0 ; i < n; i++){if(in[i] == 0){q.push(i);}}// (2) BFSwhile(q.size()){int tmp = q.front();q.pop();ret.push_back(tmp);// 修改相连结点的边for(auto& e : edges[tmp]){in[e]--;if(in[e] == 0){q.push(e);}}}// 判断结果并返回if(ret.size() == n){return ret;}else{return {};}
}
3.火星词典
1.题目链接
- 火星词典
2.算法原理详解
- 本题问题可以转化为: - 能否拓扑排序?
- 是否是有向无环图?/ 有向图中是否有环?
 
- 如何搜索信息? - 两层for循环枚举出所有的两个字符串的组合
- 利用双指针,根据字典序规则找出信息
 
- 两层
- 流程: - 建图:unordered_map<char, unordered_set<char>> edges
- 统计入度信息:unordered_map<char, int> in- 此哈希表必须要初始化,否则入度表里没有任何字符的信息,BFS时,无法将入度为0的结点入队列
 
- 拓扑排序
 
- 建图:
- 细节问题:类似abc和ab这两个字符串- 不管在哪儿,都应该是ab的字典序小于abc的字典序
 
- 不管在哪儿,都应该是
3.代码实现
class Solution 
{unordered_map<char, unordered_set<char>> edges; // 邻接表unordered_map<char, int> in; // 入度表bool check = false; // 处理边界情况
public:string alienOrder(vector<string>& words) {// 1.初始化入度表for(auto& str : words){for(auto& ch : str){in[ch] = 0;}}// 2.枚举搜集字典信息 + 建图int n = words.size();for(int i = 0; i < n; i++){for(int j = i + 1; j < n; j++){AddInfo(words[i], words[j]);if(check){return "";}}}// 3. 拓扑排序string ret;queue<char> q;// (1) 入度为0的入队列for(auto& [ch, count] : in){if(count == 0){q.push(ch);}}// BFSwhile(q.size()){char tmp = q.front();q.pop();ret += tmp;// 修改相邻点的边for(auto& ch : edges[tmp]){if(--in[ch] == 0){q.push(ch);}}}// 检验是否有环for(auto& [ch, count] : in){if(count != 0){return "";}}return ret;}void AddInfo(const string& s1, const string& s2){int n = min(s1.size(), s2.size());int i = 0;while(i < n){if(s1[i] != s2[i]) {char a = s1[i], b = s2[i];// 避免数据冗余if(!edges.count(a) || !edges[a].count(b)){edges[a].insert(b);  // s1[i] -> s2[i]in[b]++;}break;}i++;}// 边界情况处理if(i == s2.size() && i < s1.size()){check = true;}}
};