拓扑排序
一、拓扑排序的定义
__拓扑排序是一个有向无环图的所有顶点的一种线性排序,使得对于顶点u到顶点v的每个有向边u
二、DFS求拓扑排序
1、先看dfs前序和后序遍历、逆后序遍历的实现 伪代码
void dfs(Digraph G, int v)
{
pre.push_back(v);//前序遍历
marked[v] = true;
for(int ne : G.adj(v)) {
if(!marked[ne])
dfs(G, ne)
}
Post.push_back(v);//后序排序
}
int main(int argc, char*argv[])
{
dfs(G, 0);
reverse(Post.begin(), Post.end());//逆后序排序--即拓扑排序
}
使用dfs求拓扑排序这里需要用到一个命题:一副有向无环图的拓扑排序顺序即为所有顶点的逆后序排序。
证明:对于任意一条边 v
1、dfs(w)已经被调用且返回,w已在Post队列中
2、dfs(w)还没被调用, mark[w]还未被标记,那么根据 v
3、dfs(w)被调用,mark[w]被标记且未返回。这种情况在有向无环图中不存在。反证法:当存在时,根据递归调用链意味着存在w
三、DFS判断有向图是否存在环
根据DFS拓扑排序的证明中,可以得知v
//寻找有向环
class DirectedCycle
{
public:
DirectedCycle(Digraph G)
{
marked_.resize(G.V());
edge_to_.resize(G.V());
on_stack_.resize(G.V());
for(int v = 0; v < G.V(); v++)
{
if(!marked_[v]) dfs(G, v);
}
}
void dfs(Digraph G, int v)
{
marked_[v] = true;
on_stack_[v] = true;
for(int w : G.Adj(v))
{
if(!marked_[w])
{
edge_to_[w] = v;// 这样可以用通过叶子结点回溯到父结点, 然后通过栈形式先进后出反转
dfs(G, w);
}
else if(on_stack_[w])
{
//找到一个环,当前顶点为w
//根据edge_to_访问前后关系找到v->w中间的顶点
for(int x = v; x != w; x = edge_to_[x])
{
cycle_.push(x);
}
cycle_.push(w);
cycle_.push(v);
}
on_stack_[v] = false;
}
}
bool HasCycle(){ return !cycle_.empty(); }
std::stack<int> Cycle(){ return cycle_; }
private:
std::vector<int> marked_;
std::vector<int> edge_to_;
std::vector<int> on_stack_;
std::stack<int> cycle_;
};
四、BFS求拓扑排序(Kahn's algorithm)
在一个顶点的有向图中,一个顶点的「出度」指的是该顶点指出的边的总数,一个顶点的「入度」为指向该顶点的边的总数,目前个人觉得bfs算法相比dfs求拓扑排序更加直观。
算法的主要思想:每次找到入度为0的顶点,把该顶点加入排序中,并对指向点的入度-1
算法流程:
1、统计每个顶点的入度
2、找到入度为0的顶点并加入队列中
3、每次从队列中移除顶点,并把指向的顶点入度减少1,如果入度减少到0,则加入队列中,直到队列为空
vector<int> bfs(int n, vector<vector<int>> edges) {
//根据边建图
//1.统计每个顶点的入度
vector<vector<int>> graph(n);
vector<int> ind(n, 0);
for(auto&edge : edges){
graph[edge[0]].push_back(edge[1]);
ind[edge[1]]++;
}
queue<int> q;
vector<int> res;
//2.找到一开始入度为0的顶点
for(int i = 0; i < n; ++i)
{
if(ind[i] == 0) {
q.push(i);
}
}
while(!q.empty()) {
int x = q.front();
q.pop();
res.push_back(x);
for(int ne : graph[x]){
if(--ind[ne] == 0) {
q.push(ne);
}
}
}
return res;
}
五 、根据BFS拓扑排序判断存在环
使用bfs对有向图进行拓扑排序,如果能够将所有节点都进行排序, 那么有向图中不存在环,否则则存在环。修改上面的判断条件即可
bool HasCycle(int n, vector<vector<int>> edges) {
//根据边建图
//1.统计每个顶点的入度
vector<vector<int>> graph(n);
vector<int> ind(n, 0);
for(auto&edge : edges){
graph[edge[0]].push_back(edge[1]);
ind[edge[1]]++;
}
queue<int> q;
int cnt = 0;//统计已排序顶点数量
//2.找到一开始入度为0的顶点
for(int i = 0; i < n; ++i)
{
if(ind[i] == 0) {
q.push(i);
}
}
while(!q.empty()) {
int x = q.front();
q.pop();
cnt++;
for(int ne : graph[x]){
if(--ind[ne] == 0) {
q.push(ne);
}
}
}
return cnt == n; //判断是否已对所有顶点排序即可
}
六、相关题目
2115. 从给定原材料中找到所有可以做出的菜
1462. 课程表 IV
1136. 并行课程
七 、总结
1、拓扑排序存在的前提是有向图中不存在环
2、求解拓扑排序可以通过求dfs的逆后序排序和通过bfs入度来进行查找
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App