图论基础之拓扑排序

拓扑排序

定义

拓扑排序要解决的问题是如何给一个有向无环图的所有节点排序 拓扑排序要解决的问题是如何给一个有向无环图的所有节点排序 拓扑排序要解决的问题是如何给一个有向无环图的所有节点排序

在这里插入图片描述

我们可以拿大学每学期排课的例子来描述这个过程 我们可以拿大学每学期排课的例子来描述这个过程 我们可以拿大学每学期排课的例子来描述这个过程
比如学习大学课程中有「程序设计」「算法语言」「高等数学」「离散数学」「编译技术」「普通物理」 比如学习大学课程中有「程序设计」「算法语言」「高等数学」「离散数学」「编译技术」「普通物理」 比如学习大学课程中有「程序设计」「算法语言」「高等数学」「离散数学」「编译技术」「普通物理」
「数据结构」 , 「数据库系统」等。按照例子中的排课,当我们想要学习「数据结构」的时候 「数据结构」,「数据库系统」等。按照例子中的排课,当我们想要学习「数据结构」的时候 「数据结构」,「数据库系统」等。按照例子中的排课,当我们想要学习「数据结构」的时候
就必须先学会「离散数学」 , 学习完这门课后就获得了学习「编译技术」的前置条件 就必须先学会「离散数学」,学习完这门课后就获得了学习「编译技术」的前置条件 就必须先学会「离散数学」,学习完这门课后就获得了学习「编译技术」的前置条件
当然,「编译技术」还有一个更加前的课程「算法语言」 当然,「编译技术」还有一个更加前的课程「算法语言」 当然,「编译技术」还有一个更加前的课程「算法语言」
这些课程就相当于几个顶点 u , 顶点之间的有向边 ( u , v ) 就相当于学习课程的顺序 这些课程就相当于几个顶点u,顶点之间的有向边 (u,v) 就相当于学习课程的顺序 这些课程就相当于几个顶点u,顶点之间的有向边(u,v)就相当于学习课程的顺序
教务处安排这些课程 , 使得在逻辑关系符合的情况下排出课表 教务处安排这些课程,使得在逻辑关系符合的情况下排出课表 教务处安排这些课程,使得在逻辑关系符合的情况下排出课表
就是拓扑排序的过程 就是拓扑排序的过程 就是拓扑排序的过程

但是如果某一天排课的老师打瞌睡了,说想要学习数据结构,还得先学操作系统 但是如果某一天排课的老师打瞌睡了,说想要学习 数据结构,还得先学 操作系统 但是如果某一天排课的老师打瞌睡了,说想要学习数据结构,还得先学操作系统
而操作系统的前置课程又是数据结构,那么到底应该先学哪一个 ( 不考虑同时学习的情况 ) ? 而 操作系统 的前置课程又是 数据结构,那么到底应该先学哪一个(不考虑同时学习的情况)? 而操作系统的前置课程又是数据结构,那么到底应该先学哪一个(不考虑同时学习的情况)?
在这里,数据结构和操作系统间就出现了一个环 在这里,数据结构 和 操作系统 间就出现了一个环 在这里,数据结构和操作系统间就出现了一个环
显然同学们现在没办法弄清楚自己需要先学什么了 , 也就没办法进行拓扑排序了 显然同学们现在没办法弄清楚自己需要先学什么了,也就没办法进行拓扑排序了 显然同学们现在没办法弄清楚自己需要先学什么了,也就没办法进行拓扑排序了
因为如果有向图中存在环路 , 那么我们就没办法进行拓扑排序 因为如果有向图中存在环路,那么我们就没办法进行拓扑排序 因为如果有向图中存在环路,那么我们就没办法进行拓扑排序

因此我们可以说在一个 D A G ( 有向无环图 ) 中 , 我们将图中的顶点以线性方式进行排序 因此我们可以说在一个DAG(有向无环图)中,我们将图中的顶点以线性方式进行排序 因此我们可以说在一个DAG(有向无环图),我们将图中的顶点以线性方式进行排序
使得对于任何的顶点 u 到 v 的有向边 ( u , v ) , 都可以有 u 在 v 的前面 使得对于任何的顶点u到v的有向边(u,v),都可以有u在v的前面 使得对于任何的顶点uv的有向边(u,v),都可以有uv的前面

还有给定一个 D A G , 如果从 i 到 j 有边 , 则认为 j 依赖于 i 还有给定一个DAG,如果从 i 到 j 有边,则认为j依赖于i 还有给定一个DAG,如果从ij有边,则认为j依赖于i
如果 i 到 j 有路径 ( i 可达 j ) , 则称 j 间接依赖于 i 如果i到j有路径(i可达j),则称 j 间接依赖于i 如果ij有路径(i可达j),则称j间接依赖于i

拓扑排序的目标是将所有节点排序 , 使得排在前面的节点不能依赖于排在后面的节点 拓扑排序的目标是将所有节点排序,使得排在前面的节点不能依赖于排在后面的节点 拓扑排序的目标是将所有节点排序,使得排在前面的节点不能依赖于排在后面的节点

特点

拓扑排序是不唯一的 拓扑排序是不唯一的 拓扑排序是不唯一的

举例 : 如定义的图 举例:如定义的图 举例:如定义的图

入度为 0 的节点有高等数学和程序设计,我先排高等数学和先排程序设计都是等价的 入度为0的节点有高等数学和程序设计,我先排高等数学和先排程序设计都是等价的 入度为0的节点有高等数学和程序设计,我先排高等数学和先排程序设计都是等价的

算法具体过程

先建图 , 在建图的过程中统计每个点的入度 , 我们用队列去进行拓扑排序 先建图,在建图的过程中统计每个点的入度,我们用队列去进行拓扑排序 先建图,在建图的过程中统计每个点的入度,我们用队列去进行拓扑排序

先扫一遍点,找出入度为 0 的点 ( 拓扑排序必有入读为 0 的点 ) , 然后放入队列 先扫一遍点,找出入度为0的点(拓扑排序必有入读为0的点), 然后放入队列 先扫一遍点,找出入度为0的点(拓扑排序必有入读为0的点),然后放入队列

然后进行 b f s , 先取出队列首端 , 然后遍历子节点 , 把子节点的入度 − 1 , 当子节点入度为 0 的时候放入队列 然后进行bfs,先取出队列首端,然后遍历子节点,把子节点的入度-1,当子节点入度为0的时候放入队列 然后进行bfs,先取出队列首端,然后遍历子节点,把子节点的入度1,当子节点入度为0的时候放入队列

对于统计拓扑序我们可以用队列 , 数组 , 或者 v e c t o r 在队列首端弹出的时候存起来 对于统计拓扑序我们可以用队列,数组,或者vector在队列首端弹出的时候存起来 对于统计拓扑序我们可以用队列,数组,或者vector在队列首端弹出的时候存起来

int n,m;
int d[N],len[N],f[N];
vector<int>a[N];
queue<int>q;
queue<int>p;
void topsort(){
     for(int i = 1;i <= n;i++){
        if(!d[i]){
            q.push(i);
        }
     }
     while(!q.empty()){
        int t = q.front();
        q.pop();
        p.push(t);
        for(auto j : a[t]){
            d[j]--;
            if(d[j] == 0){
                q.push(j);
            }
        }
    }
    return;
}

应用

1 : 用于解决依赖性的排序问题 1: 用于解决依赖性的排序问题 1:用于解决依赖性的排序问题

2 : 拓扑排序判环 , 记录队列取出首端的次数,如果不为总节点数则存在环 2: 拓扑排序判环,记录队列取出首端的次数,如果不为总节点数则存在环 2:拓扑排序判环,记录队列取出首端的次数,如果不为总节点数则存在环

例题

在这里插入图片描述
代码:


#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e5+10;

int n,m;
int d[N],len[N],f[N];
vector<int>a[N];
//如果题目有对节点大小有限制,用优先队列priority_queue<int>q或者priority_queue<int,vector<int>,greater<int>>q
queue<int>q;
queue<int>p;
void topsort(){
     for(int i = 1;i <= n;i++){
        if(!d[i]){
            q.push(i);
        }
     }
     while(!q.empty()){
        int t = q.front();
        q.pop();
        p.push(t);
        for(auto j : a[t]){
            d[j]--;
            if(d[j] == 0){
                q.push(j);
            }
        }
    }
    return;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n;
    for(int i = 1;i <= n;i++){
        int u;
        while(cin >> u && u){
            a[i].push_back(u);
            d[u]++;
        }
    }
    topsort();
    while(!p.empty()){
         cout << p.front() << " ";
         p.pop();
    }
    return 0;
}

题单

序号题号标题题型难度评级题解
1luogu B3644[模板]拓扑排序 / 家谱树正常拓扑排序⭐⭐👍
2HDU-1285确定比赛名次优先队列拓扑排序⭐⭐👍
3HDU-4857逃生反向建图+优先队列⭐⭐⭐👍
4luogu P1347排序拓扑排序判环⭐⭐⭐👍
5luogu P3243菜肴制作反向建图+优先队列⭐⭐⭐👍
6luogu P1113杂物拓扑思想应用⭐⭐⭐👍
7luogu P1685游览拓扑思想应用⭐⭐⭐👍
8luogu P1983车站分级拓扑思想应用⭐⭐⭐👍
9luogu P1038神经网络拓扑思想应用⭐⭐⭐👍
10luogu P4017最大食物链计数拓扑思想应用⭐⭐⭐👍
11Codeforces 1931FChat Screenshots拓扑排序判环⭐⭐👍
12luogu P1954航空管制拓扑思想+优先队列+反向建图⭐⭐⭐ ⭐👍
  • 8
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值