目录
- 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;}}
};