有向图的拓扑排序-BFS求解

有向图的拓扑排序-BFS求解

题目描述

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

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


输入格式

  • 第一行包含两个整数 nm
  • 接下来 m 行,每行包含两个整数 xy ,表示点 x 和点 y 之间存在一条有向边 (x, y)

输出格式

  • 共一行,如果存在拓扑序列,则输出拓扑序列。
  • 否则输出 -1

数据范围

  • 1 ≤ n , m ≤ 10^5

输入样例

复制

3 3
1 2
2 3
1 3

输出样例

复制

1 2 3

解题思路

解题思路:

我们首先要记录每个结点的入度,即每个结点有几条边指向它,然后找出所有入度为0的结点,将其放入队列,那么从这个结点开始,将它指向其他结点的边删去。如果删去这条边后,原本有这个结点指向的那个结点入度为0(即只有开始这个结点指向它),那么就将其入队。直到遍历到最后一个结点,如果尾指针不等于n,就说明没有经过所有点,那么这条路径就不符合拓扑排序。再重复上述操作,直到所有入度为0的点都枚举完都没找到,那么这个无向图就没有拓扑排序。

  • 一篇很清楚的笔记。

代码实现

c 复制代码
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1e5 + 10;
int h[N], e[N], ne[N];
int q[N], d[N];
int n, m, idx;
void add(int a, int b)
{
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx++;
}
bool topsort()
{
	int hh = 0, tt = 0;
		
	for (int i = 1; i <= n; i++)
	{
		if (d[i] == 0)
		{
			q[tt++] = i; //找到没有被其他结点指向的元素,将其入队
		}
	}
 
	while (hh <= tt)
	{
		int k = q[hh++];
		for (int i = h[k]; i != -1; i = ne[i])
		{
			int j = e[i];
			d[j]--;   //删去由k指向j的这条边
			if (d[j] == 0) //如果发现该节点入度为0,即没有其他结点指向他了,就将其入队
				q[tt++] = j;
		}
	}
	return tt == n; //如果tt=n就表示每个结点都走到了,就是一个拓扑排序
}
int main()
{
	cin >> n >> m;
	memset(h, -1, sizeof h);
	while (m--)
	{
		int a, b;
		cin >> a >> b;
		add(a, b);
		d[b]++; //入度,即记录b这个结点有几条边指向他
	}
	if (topsort())
	{
		for (int i = 0; i < n; i++)
		{
			cout << q[i] << " ";
		}
	}
	else
		cout << "-1" << endl;
	return 0;
}

代码思路总结

该题是一个典型的拓扑排序 问题,用于求解有向无环图(DAG)的拓扑序列。拓扑排序的核心思想是通过 BFS(广度优先搜索)DFS(深度优先搜索) 来实现。本题使用的是 BFS 方法,具体实现基于 Kahn 算法


1. 图的表示

  • 使用邻接表 存储图:
    • h[N]h[i] 表示节点 i 的第一条边的索引。
    • e[N]e[i] 表示第 i 条边的终点。
    • ne[N]ne[i] 表示第 i 条边的下一条边的索引。
    • idx:用于给每条边分配一个唯一的索引。
  • 使用数组 d[N] 记录每个节点的入度(即有多少条边指向该节点)。

2. 插入边

  • add(int a, int b) 函数用于将一条从节点 a 指向节点 b 的边插入到邻接表中。
  • 使用头插法 将新边插入到邻接表中:
    • e[idx] = b:记录边的终点。
    • ne[idx] = h[a]:将新边的下一条边指向 h[a]
    • h[a] = idx++:更新 h[a] 为当前边的索引,并递增 idx

3. 拓扑排序实现

  • 初始化
    • 使用队列 q[N] 存储入度为 0 的节点。
    • 遍历所有节点,将入度为 0 的节点加入队列。
  • BFS 过程
    • 从队列中取出队头节点 k,将其加入拓扑序列。
    • 遍历从 k 出发的所有边,减少邻接节点 j 的入度。
    • 如果邻接节点 j 的入度变为 0,则将其加入队列。
  • 判断是否存在拓扑序列
    • 如果最终队列中的节点数等于 n,则说明图中没有环,拓扑序列存在。
    • 否则,说明图中存在环,无法构造拓扑序列。

4. 主函数

  • 读取输入的节点数 n 和边数 m
  • 初始化邻接表 h-1,表示所有节点的第一条边初始为空。
  • 读取每条边并调用 add 函数插入到邻接表中,同时更新节点的入度。
  • 调用 topsort() 函数判断是否存在拓扑序列:
    • 如果存在,输出拓扑序列。
    • 否则,输出 -1

5. 复杂度分析

  • 时间复杂度 :O(n + m),其中 n 是节点数,m 是边数。每个节点和每条边都只会被访问一次。
  • 空间复杂度:O(n + m),用于存储邻接表和队列。

6. 代码细节

  • 入队条件
    • 只有入度为 0 的节点才会被加入队列。
  • 队列的实现
    • 使用数组 q[N] 模拟队列,hh 表示队头,tt 表示队尾。
  • 拓扑序列的存储
    • 队列 q 中存储的节点顺序就是拓扑序列。

7. 示例运行

输入:

复制

3 3
1 2
2 3
1 3
执行过程:
  1. 初始化:
    • 邻接表:
      • h[1] 指向边 (1, 2)(1, 3)
      • h[2] 指向边 (2, 3)
      • h[3] 为空。
    • 入度数组:
      • d[1] = 0d[2] = 1d[3] = 2
  2. 将入度为 0 的节点 1 加入队列。
  3. 处理队列:
    • 取出节点 1,加入拓扑序列。
    • 减少节点 23 的入度:
      • d[2] = 0,将 2 加入队列。
      • d[3] = 1
    • 取出节点 2,加入拓扑序列。
    • 减少节点 3 的入度:
      • d[3] = 0,将 3 加入队列。
    • 取出节点 3,加入拓扑序列。
  4. 最终拓扑序列为 1 2 3
输出:
1 2 3

8. 总结

  • 该代码通过 BFS 实现了拓扑排序,适用于大规模有向无环图。
  • 使用邻接表存储图,适合处理稀疏图。
  • 通过维护入度数组和队列,高效地实现了拓扑排序的核心逻辑。
  • 如果图中存在环,则无法构造拓扑序列,输出 -1

算法刷题记录

相关推荐
Luis Li 的猫猫2 小时前
基于Matlab的多目标粒子群优化
算法·matlab
天天向上杰3 小时前
浅显易懂HashMap的数据结构
java·数据结构·算法·哈希算法·hashmap·hash
Reese_Cool6 小时前
【洛谷贪心算法题】P2240部分背包问题
c++·算法·贪心算法·蓝桥杯
楼台的春风6 小时前
【图像平移、旋转、仿射变换、投影变换】
图像处理·人工智能·深度学习·opencv·算法·计算机视觉·matlab
Liudef066 小时前
Stable Diffusion(SD)系列模型及关联算法深度解析
算法·stable diffusion
S01d13r6 小时前
LeetCode 解题思路 6(Hot 100)
算法·leetcode·职场和发展
sensen_kiss6 小时前
INT202 Complexity of Algroithms 算法的复杂度
数据结构·学习·算法
黑色的山岗在沉睡6 小时前
P10265 [GESP样题 七级] 迷宫统计
算法
王RuaRua6 小时前
1.C语言初识
c语言·开发语言·算法
李洋-蛟龙腾飞公司6 小时前
HarmonyOS NEXT 原生应用/元服务-自定义运行/调试配置
算法·华为