拓扑排序【模板】

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
 

题目描述

给定一个包含nnn个点mmm条边的有向无环图,求出该图的拓扑序。若图的拓扑序不唯一,输出任意合法的拓扑序即可。若该图不能拓扑排序,输出−1-1−1。

输入描述:

第一行输入两个整数n,mn,mn,m ( 1≤n,m≤2⋅1051\le n,m \le 2\cdot 10^51≤n,m≤2⋅105),表示点的个数和边的条数。
接下来的mmm行,每行输入两个整数ui,viu_i,v_iui​,vi​ (1≤u,v≤n1\le u,v \le n1≤u,v≤n),表示uiu_iui​到viv_ivi​之间有一条有向边。

输出描述:

若图存在拓扑序,输出一行nnn个整数,表示拓扑序。否则输出−1-1−1。

输入:

5 4
1 2
2 3
3 4
4 5

输出:

1 2 3 4 5

答案:

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 100;
int din[N];
queue<int >q;//队列用来存入度为零的点
vector<int >tp;//用来存出队的点,最终结果。
vector<int>e[N];//存图
int n,m;
void solve()
{
    for(int i=1;i<=n;i++){
        if(!din[i])q.push(i);
    }
    while(q.size()) {
        int u=q.front();
        tp.push_back(u);
        q.pop();
        for(auto it:e[u]){//auto it:e[u]是循环体的一种表示将e[u]中全部节点都执行一遍循环体
            --din[it];//it代表节点
            if(!din[it])
                q.push(it);
        }
    }
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int u,v;
        cin>>u>>v;
        e[u].push_back(v);
        ++din[v];
    }
    solve();
    if(tp.size()!=n)puts("-1");
    else{
        for(int i=0;i<n;i++){
            cout<<tp[i];
            if(i!=n-1)cout<<" ";
        }
    }
    return 0; 
}

解释ai:

这段代码实现的是一个拓扑排序算法,用于处理有向无环图(DAG)的顶点排序问题。拓扑排序是针对一个DAG的所有顶点的线性排序,使得对于从顶点u到顶点v的每条有向边(u, v),u都在v之前。这个算法在很多领域都有应用,比如任务调度、课程安排等。

下面是代码的详细解析:

  1. 头文件和命名空间
    • #include<bits/stdc++.h>:这是一个非标准的头文件,包含了几乎所有C++标准库的内容,但在竞赛编程中常用以简化代码。
    • using namespace std;:使用标准命名空间,避免在调用标准库时重复写std::
  2. 类型定义
    • ull:无符号长整型(unsigned long long)。
    • ll:长整型(long long)。
    • pii:整型对(pair<int, int>)。
  3. 常量定义
    • N:顶点的最大数量。
    • inf:无穷大值,用于表示某些特殊状态。
    • mod:取模的值,常用于防止整数溢出或计算哈希值。
  4. 全局变量
    • dx[]dy[]:用于二维网格的行列移动,但在这段代码中未使用。
    • din[]:记录每个顶点的入度(即有多少条边指向该顶点)。
    • q:用于拓扑排序的队列,存储当前入度为0的顶点。
    • tp:存储拓扑排序结果的向量。
    • e[]:邻接表,存储图的信息。
  5. 函数定义
    • solve():实现拓扑排序的核心函数。首先将所有入度为0的顶点加入队列,然后不断从队列中取出顶点,将其加入结果向量tp,并减少其相邻顶点的入度。如果相邻顶点的入度变为0,则将其加入队列。
  6. 主函数main):
    • 输入顶点数n和边数m
    • 读取每条边的起点和终点,构建图并计算每个顶点的入度。
    • 调用solve()函数进行拓扑排序。
    • 如果拓扑排序的结果向量tp中的顶点数不等于顶点总数n,说明图中存在环,输出"-1";否则,输出拓扑排序的结果。

注意:这段代码假设输入的图是合法的DAG,即不存在环。如果存在环,拓扑排序无法进行,因此会输出"-1"。此外,这段代码在处理边时只增加了终点的入度,没有建立从起点到终点的直接连接(因为拓扑排序只关心入度),这是邻接表的一种常见用法。

            在C++中,auto it: e[u] 这种写法实际上是 for 循环中的一部分,但它本身是不完整的。完整的写法应该是 for(auto it : e[u]),这是C++11及以后版本中引入的基于范围的 for 循环(range-based for loop)的语法。

这个循环的作用是遍历容器(如 std::vectorstd::liststd::deque 等)或任何提供了 begin() 和 end() 成员函数(或全局函数)的序列的所有元素。在你的上下文中,e[u] 应该是一个容器(比如 std::vector<int>),它存储了与顶点 u 相邻的所有顶点的索引或标识符。

因此,for(auto it : e[u]) 的意思是:“对于 e[u] 容器中的每一个元素(我们称之为 it),都执行一次循环体中的代码”。这里的 it 是自动类型推导的,它的类型与 e[u] 容器中的元素类型相同。

在你的代码中,e 是一个 std::vector<std::vector<int>> 类型的邻接表,其中 e[u] 表示顶点 u 的所有邻接顶点列表。因此,for(auto it : e[u]) 就是在遍历顶点 u 的所有邻接顶点,并且对于每一个邻接顶点 it,都会执行循环体内的代码,即减少该顶点的入度,并检查是否需要将其加入队列 q 中。

在您的代码片段中,for(auto it:e[u]) 循环遍历了顶点 u 的所有邻接顶点(即所有从 u 出发可以到达的顶点)。对于 u 的每一个邻接顶点 it,代码执行了两个关键操作:

  1. --din[it];:这行代码将顶点 it 的入度(din[it])减一。这是因为我们刚刚从顶点 u 到顶点 it 移除了一条边(在拓扑排序的上下文中,这通常意味着我们“处理”了顶点 u,并考虑到了它指向的所有顶点),所以顶点 it 的入度应该减少。

  2. if(!din[it]) q.push(it);:这行代码检查顶点 it 的入度是否变为了 0。如果是,那么顶点 it 现在没有任何入边(即没有其他顶点指向它),这意味着它可以在拓扑排序的结果序列中被考虑。因此,我们将顶点 it 加入队列 q 中,以便稍后在拓扑排序的结果序列中进一步处理它。

这个过程是拓扑排序算法的核心。拓扑排序算法依赖于一个事实:在一个有向无环图(DAG)中,如果我们不断地移除入度为 0 的顶点以及从它们出发的所有边,那么最终我们将会移除图中的所有顶点(前提是图是连通的,或者我们分别处理图的所有连通分量)。这个过程中顶点的移除顺序就是一个有效的拓扑排序。

总结来说,这段代码通过不断地从图中移除入度为 0 的顶点(以及从它们出发的边),来构建图的拓扑排序。如果最终我们能够移除图中的所有顶点,那么图就是一个DAG,并且我们得到了一个有效的拓扑排序;如果过程中我们发现某个顶点无法被移除(即其入度始终不为 0),那么图中就存在环,无法进行拓扑排序。

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值