拓扑排序模板题:洛谷-家谱树

原题链接:B3644 【模板】拓扑排序 / 家谱树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目:

题目描述

输入格式

输出格式

输入输出样例

思路:

AC代码:


题目:

题目描述

有个人的家族很大,辈分关系很混乱,请你帮整理一下这种关系。给出每个人的后代的信息。输出一个序列,使得每个人的后辈都比那个人后列出。

输入格式

第 1 行一个整数 𝑁(1≤𝑁≤100),表示家族的人数。接下来 𝑁 行,第 𝑖 行描述第 𝑖 个人的后代编号 𝑎𝑖,𝑗,表示 𝑎𝑖,𝑗是 𝑖 的后代。每行最后是 0 表示描述完毕。

输出格式

输出一个序列,使得每个人的后辈都比那个人后列出。如果有多种不同的序列,输出任意一种即可。

输入输出样例

输入

复制代码
5
0
4 5 1 0
1 0
5 3 0
3 0

输出

复制代码
2 4 

思路:

这道题是拓扑排序的模板题,所以思路主要介绍拓扑排序。

拓扑排序并不是类似于快排、冒泡给一串数字从大到小或者从小到大排序。拓扑排序的目的是:在一张有向无环图中,排出一个序列满足:图中的每一条有向边{x, y},x 在我们排出的序列中都出现在 y 之前,得到的序列我们成为拓扑序列。

为什么一定是有向无环图呢?这个问题留到后面回答。

拓扑排序的思想是:每次选中入度为0的点,删除这个点和它的出边,并把它加入拓扑序列

我们对这张有向无环图进行拓扑排序:

1.选中A,删除A与A的出边,把A加入拓扑序列,图片如下:

2.B和E都是入度为0的点,我们任意选一个就可以了,不难看出,拓扑序列不是唯一的,可能有多种。这里我们选B。

3.接下来选E,重复上述操作。当前的拓扑序列是{A,B,E}.

4.不难看出,最后得到的拓扑序列是{A,B,E,D,C}.

我们回到刚才的问题: 为什么一定是有向无环图呢?

我们以这张图为例:

接下来进行拓扑排序:

按照拓扑排序的思想,我们应该找到一个入度为0的点,而在这张图中我们找不到入度为0的点,这是因为图中出现了环。我们对它进行拓扑排序,最后得到的是一个空的拓扑序列。

根据这个特点,我们可以使用拓扑排序来判断图中是否有环,即根据最后得到的拓扑序列中元素个数是否与图中结点的个数相等。如果相等,即无环,不相等则有环。

拓扑排序的模板代码:

cpp 复制代码
int h[N], e[M], nx[M], tp[N];
int idx = 0, cnt = 0;
int dx[N];
int n;

void add(int x, int y) {
	e[idx] = y;
	nx[idx] = h[x];
	h[x] = idx;
	idx++;
	dx[y]++;//出度加一
}

bool topsort() {
	queue<int>q;
	for (int i = 1; i <= n; i++) {
		if (dx[i] == 0) q.push(i);//入度为0则入队
	}

	while (!q.empty()) {
		int x = q.front(); q.pop();
		tp[cnt++] = x;
		for (int i = h[x]; i != -1; i = nx[i]) {
			if (--dx[e[i]] == 0) q.push(e[i]);
		}
	}

	return cnt == n;//判断是否有环
}

代码的核心思想是使用队列来维护一个入度为0的结点的集合。

其中dx[x]是结点x的入度,tp[]存放拓扑序列,使用数据模拟邻接表的方法存储图。

不了解数组模拟邻接表的可以看我上一篇博客:AcWing-1562.微博转发-再谈图的存储结构-CSDN博客

AC代码:

cpp 复制代码
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#define MEM(x, y) memset(x, y, sizeof x)
using namespace std;

const int N = 105, M = 1e4 + 5;
int h[N], e[M], nx[M], tp[N];
int idx = 0, cnt = 0;
int dx[N];
int n;

void add(int x, int y) {
	e[idx] = y;
	nx[idx] = h[x];
	h[x] = idx;
	idx++;
	dx[y]++;
}

bool topsort() {
	queue<int>q;
	for (int i = 1; i <= n; i++) {
		if (dx[i] == 0) q.push(i);
	}

	while (!q.empty()) {
		int x = q.front(); q.pop();
		tp[cnt++] = x;
		for (int i = h[x]; i != -1; i = nx[i]) {
			if (--dx[e[i]] == 0) q.push(e[i]);
		}
	}

	return cnt == n;
}


int main() {
	MEM(h, -1);
	MEM(dx, 0);
	cin >> n;
	for (int i = 1; i <= n; i++) {
		int temp = 0; cin >> temp;
		while (temp != 0) {
			add(i, temp);
			cin >> temp;
		}
	}

	topsort();

	for (int i = 0; i < cnt; i++) {
		cout << tp[i] << " ";
	}
	cout << endl;
	
	return 0;
}

此题还有更多更优解法,不再一一介绍了,文章尚有不足,欢迎大佬们指正。

相关推荐
飞桨PaddlePaddle1 小时前
Wan2.1和HunyuanVideo文生视频模型算法解析与功能体验丨前沿多模态模型开发与应用实战第六期
人工智能·算法·百度·音视频·paddlepaddle·飞桨·deepseek
Starry_hello world2 小时前
C++ 快速幂算法
c++·算法·有问必答
石去皿3 小时前
力扣hot100 91-100记录
算法·leetcode·职场和发展
SsummerC4 小时前
【leetcode100】组合总和Ⅳ
数据结构·python·算法·leetcode·动态规划
尤物程序猿5 小时前
【2025面试Java常问八股之redis】zset数据结构的实现,跳表和B+树的对比
数据结构·redis·面试
2301_807611495 小时前
77. 组合
c++·算法·leetcode·深度优先·回溯
微网兔子6 小时前
伺服器用什么语言开发呢?做什么用什么?
服务器·c++·后端·游戏
YuforiaCode6 小时前
第十三届蓝桥杯 2022 C/C++组 修剪灌木
c语言·c++·蓝桥杯
YOULANSHENGMENG6 小时前
linux 下python 调用c++的动态库的方法
c++·python
CodeWithMe6 小时前
【C++】STL之deque
开发语言·c++