拓扑排序
拓扑排序用来解决什么问题
解决相互依赖的条件下的排序问题。
比如:
- 早上起来你只穿了内裤
- 你还需要穿:秋衣、秋裤、毛衣、毛裤、羽绒服、外裤、袜子、鞋子
此时穿衣顺序上就有了两两之间的局部依赖,如:
- 内裤 --> 秋裤 --> 毛裤 --> 外裤
- 秋衣 --> 毛衣 --> 羽绒服
- 袜子 --> 鞋子
当然有的人喜欢先穿毛裤再来秋裤(逃)
最终的穿衣顺序可能为:
- 内裤 --> 秋裤 --> 毛裤 --> 外裤 --> 球衣 --> 毛衣 --> 羽绒服 --> 袜子 --> 鞋子
- 内裤 --> 秋衣 --> 毛衣 --> 羽绒服 --> 秋裤 --> 毛裤 --> 外裤 --> 袜子 --> 鞋子
是的,拓扑排序有可能有多种结果
拓扑排序实现
需要用到的数据结构
如果 a 先于 b 执行,也就是说 b 依赖于 a,那么就在顶点 a 和顶点 b 之间,构建一条从 a 指向 b 的边。而且,这个图不仅要是有向图,还要是一个有向无环图,
也就是不能存在像 a->b->c->a 这样的循环依赖关系。因为图中一旦出现环,拓扑排序就无法工作了。实际上,拓扑排序本身就是基于有向无环图的一个算法。
//存储有向图
vector<vector<int>> graph;
//存储入度
vector<int> indegree;
Kahn 算法
如果 s 需要先于 t 执行,那就添加一条 s 指向 t 的边。所以某个顶点入度为 0 就表示没有任何顶点必须先于这个顶点执行,那么这个顶点就可以执行了。我们先从图中,找出一个入度为 0 的顶点,将其输出到拓扑排序的结果序列中(对应代码中就是把它打印出来),并且把这个顶点从图中删除(也就是把这个顶点可达的顶点的入度都减 1)。我们循环执行上面的过程,直到所有的顶点都被输出。最后输出的序列,就是满足局部依赖关系的拓扑排序。
vector<int> topoSortByKahn(vector<int>& indegree, vector<vector<int>>& graph){
//将入度为0的顶点入队
queue<int> myqueue;
for (int i = 0; i < indegree.size(); i++){
if (indegree[i] == 0)
myqueue.push(i);
}
vector<int> ans;
while (!myqueue.empty()){
//删除当前节点
int temp = myqueue.front();
myqueue.pop();
ans.emplace_back(temp);
//则对应的节点入度减一
for (int i = 0; i < graph[temp].size(); i++)
{
indegree[graph[temp][i]]--;
//入度变为0,加到队列后面
if (indegree[graph[temp][i]] == 0)
myqueue.push(graph[temp][i]);
}
}
//最终的输出数 与 总节点数不同
if (ans.size() != indegree.size()) {
return {};
}
return ans;
}
DFS 算法
遍历图的所有顶点,注意是所有顶点
vector<int> findOrder(vector<vector<int>>& graph, int n) {
vector<int> visited(n, 0);
vector<int> ans;
//每次找一个未访问的节点dfs
for(int i = 0; i < n; ++i){
if(visited[i] != 0) continue;
if(!topoSortByDfs(ans, visited, graph, i)){
return {};//有环
}
}
//注意下标0的在栈底
reverse(ans.begin(), ans.end());
return ans;
}
bool topoSortByDfs(vector<int>& ans, vector<int>& visited, vector<vector<int>>& graph, int u) {
//visited : 0, 未搜索; 1, 搜索中; 2, 搜索完成;
// 将节点标记为「搜索中」
visited[u] = 1;
// 搜索其相邻节点
// 只要发现有环,立刻停止搜索
for (int v: graph[u]) {
// 如果「未搜索」那么搜索相邻节点
if (visited[v] == 0) {
if(!topoSortByDfs(ans, visited, graph, v)){
return false;//有环
}
}
// 如果「搜索中」说明找到了环
else if (visited[v] == 1) {
return false;
}
}
// 将节点标记为「已完成」
visited[u] = 2;
// 将节点入栈
ans.emplace_back(u);
return true;
}