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

原题链接: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的结点的集合。

其中dxx是结点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;
}

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

相关推荐
流年如夢6 分钟前
继承与多态_继承篇
c++·继承·面向对象·c++进阶
silvamux_18 分钟前
微头条爆款体平台适配实测:把交付要求变成可计算的指标
java·线性代数·算法·决策树·矩阵
沫璃染墨23 分钟前
从零入门计算机网络系列(十):HTTP协议进阶——从资源路径解析到状态码与请求头详解
linux·运维·服务器·网络·c++·http
xiaoye-duck41 分钟前
《C++ 工具库实战》高性能日志 spdlog:从基础使用到工程化封装
c++·第三方库
重生之小比特1 小时前
【算法拾光】前缀和|连续的子数组和 + 和相同的二元子数组
算法·leetcode·哈希算法
努力努力再努力wz1 小时前
【Lua入门系列】从 C++ 迭代器到 Lua 迭代器:一文深入理解 Lua 有状态以及无状态迭代器与泛型for、闭包、ipairs、pairs 和 next
开发语言·c++·lua
一去不复返的通信er1 小时前
力扣15:盛最多水的容器
算法·leetcode
努力实干1 小时前
第二阶段 · 第 7 课:C++ 多线程与线程安全
开发语言·c++·安全
Navigator_Z1 小时前
LeetCode //C - 1309. Decrypt String from Alphabet to Integer Mapping
c语言·算法·leetcode
潇湘秦1 小时前
SQL Server 2016+ 整数类型元数据扩宽(秒级变更)完整说明
数据库·算法