题解:AcWing 848 有向图的拓扑序列

【题目来源】

AcWing:848 有向图的拓扑序列 - AcWing题库

【题目描述】

给定一个\(n\)个点\(m\)条边的有向图,点的编号是\(1\)\(n\),图中可能存在重边和自环。请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出\(-1\)

若一个由图中所有点构成的序列\(A\)满足:对于图中的每条边\((x,y)\)\(x\)\(A\)中都出现在\(y\)之前,则称\(A\)是该图的一个拓扑序列。

【输入】

第一行包含两个整数\(n\)\(m\)

【输出】

共一行,如果存在拓扑序列,则输出任意一个合法的拓扑序列即可。否则输出\(-1\)

【输入样例】

3 3
1 2
2 3
1 3

【输出样例】

1 2 3

【核心思想】

  1. 问题分析:给定有向图 \(G=(V,E)\),其中 \(|V|=n\)\(|E|=m\)。需要找到一个拓扑序列,使得对于每条有向边 \((x,y) \in E\)\(x\) 在序列中都出现在 \(y\) 之前。如果图中存在环,则拓扑序列不存在。这是一个经典的拓扑排序问题,关键在于利用入度为 \(0\) 的节点性质进行 BFS。

  2. 算法选择

    • Kahn 算法(BFS 拓扑排序):基于贪心思想,每次选择入度为 \(0\) 的节点加入拓扑序列
    • 入度数组:维护每个节点的入度 \(d[i]\),表示指向节点 \(i\) 的边数
    • 邻接表存储:使用链式前向星或 vector 存储图结构,高效遍历邻接点
  3. 关键步骤

    • 初始化:读取 \(n\)(节点数)、\(m\)(边数),构建邻接表和入度数组 \(d[1..n]\)
    • 入队初始化:遍历所有节点 \(i\)\(1\)\(n\),将入度 \(d[i] = 0\) 的节点加入队列 \(q\)
    • BFS 处理(当队列非空时循环):
      • 取出队头节点 \(t = q[hh++]\)
      • 遍历 \(t\) 的所有邻接点 \(j\)
        • 删除边 \(t \to j\),即 \(d[j]--\)
        • \(d[j] == 0\),将 \(j\) 加入队列 \(q[++tt] = j\)
    • 判断结果
      • 若队列中处理了 \(n\) 个节点(\(tt == n-1\)),输出队列中的拓扑序列 \(q[0..n-1]\)
      • 否则图中存在环,输出 \(-1\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n + m)\),每个节点和每条边都被访问一次
    • 空间复杂度:\(O(n + m)\),邻接表存储图需要 \(O(n + m)\),队列和入度数组需要 \(O(n)\)
  5. 拓扑排序的核心思想

    • 入度为 \(0\) 的节点优先:入度为 \(0\) 的节点没有前驱约束,可以安全地加入拓扑序列
    • 删边降度:处理一个节点后,删除其所有出边,降低邻接点的入度
    • 环检测:若图中存在环,则环上所有节点的入度都 \(\geq 1\),无法被加入队列
    • BFS 贪心:每次选择当前可处理的节点(入度为 \(0\)),保证序列的合法性
    • 适用于任务调度、依赖关系处理、编译顺序确定等场景

【解题思路】

【解题思路2】

拓扑排序,有向图

【算法标签】

拓扑排序

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 100010;  // 最大节点数

int n;        // 节点数量
int m;        // 边数量
int h[N];     // 邻接表头数组
int e[N];     // 边的终点数组
int ne[N];    // 下一条边数组
int idx;      // 边的索引
int q[N];     // 队列,用于存储拓扑序列
int d[N];     // 入度数组,d[i]表示节点i的入度

// 添加有向边 a->b
void add(int a, int b)
{
    e[idx] = b;      // 存储边的终点
    ne[idx] = h[a];  // 新边指向原来的第一条边
    h[a] = idx;      // 更新头指针指向新边
    idx++;           // 边索引增加
}

// 拓扑排序函数
// 返回值:true - 存在拓扑序列,false - 存在环
bool topsort()
{
    int hh = 0;      // 队列头指针
    int tt = -1;     // 队列尾指针

    // 第一步:将所有入度为0的节点加入队列
    for (int i = 1; i <= n; i++)
    {
        if (!d[i])  // 入度为0
        {
            q[++tt] = i;  // 入队
        }
    }

    // 第二步:BFS处理队列中的节点
    while (hh <= tt)  // 队列不为空
    {
        int t = q[hh++];  // 出队

        // 遍历节点t的所有邻接点
        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];  // 邻接点j
            d[j]--;        // 删除边t->j,j的入度减1

            // 如果j的入度变为0,加入队列
            if (d[j] == 0)
            {
                q[++tt] = j;
            }
        }
    }

    // 第三步:判断是否所有节点都被处理
    // 如果队列中有n个节点,说明存在拓扑序列
    return tt == n - 1;
}

int main()
{
    // 输入节点数和边数
    cin >> n >> m;

    // 初始化邻接表
    memset(h, -1, sizeof(h));

    // 读入m条有向边
    for (int i = 0; i < m; i++)
    {
        int a, b;
        cin >> a >> b;
        add(a, b);  // 添加边 a->b
        d[b]++;     // b的入度加1
    }

    // 执行拓扑排序
    if (topsort())
    {
        // 输出拓扑序列
        for (int i = 0; i < n; i++)
        {
            printf("%d ", q[i]);
        }
        puts("");  // 换行
    }
    else
    {
        // 存在环,无法进行拓扑排序
        puts("-1");
    }

    return 0;
}
#include <bits/stdc++.h>
using namespace std;

const int N = 100010;  // 定义节点和边的最大规模

vector<int> h[N];  // 邻接表,存储图的边
int n, m, ind[N], q[N];  // n 节点数,m 边数,ind[i] 节点 i 的入度,q 队列

// 拓扑排序函数
bool topsort()
{
    int hh = 0, tt = 0;  // 队列的头和尾指针
    // 将所有入度为 0 的节点加入队列
    for (int i = 1; i <= n; i++)
        if (ind[i] == 0) q[tt++] = i;

    // 处理队列中的节点
    while (hh < tt)
    {
        int t = q[hh++];  // 取出队头节点
        // 遍历当前节点的邻接节点
        for (int i = 0; i < h[t].size(); i++)
        {
            int j = h[t][i];  // 邻接节点 j
            ind[j]--;  // 将邻接节点的入度减 1
            if (ind[j] == 0) q[tt++] = j;  // 如果入度为 0,加入队列
        }
    }
    // 如果队列中的节点数等于 n,说明存在拓扑序
    return n == tt;
}

int main()
{
    cin >> n >> m;  // 输入节点数和边数
    // 输入每条边,构建邻接表和入度数组
    while (m--)
    {
        int a, b;
        cin >> a >> b;
        h[a].push_back(b);  // 添加边 a -> b
        ind[b]++;  // 节点 b 的入度加 1
    }

    // 进行拓扑排序
    if (topsort())
    {
        // 输出拓扑序
        for (int i = 0; i < n; i++) cout << q[i] << " ";
    }
    else
    {
        // 如果不存在拓扑序,输出 -1
        cout << -1;
    }
    return 0;
}

【运行结果】

3 3
1 2
2 3
1 3
1 2 3
posted @ 2026-06-16 10:13  团爸讲算法  阅读(13)  评论(0)    收藏  举报