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

原题链接: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;
}

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

相关推荐
lgily-12259 分钟前
Python常用算法
开发语言·python·算法
闻缺陷则喜何志丹10 分钟前
【C++动态规划】1547. 切棍子的最小成本|2116
c++·算法·动态规划·力扣·最小·成本·棍子
James Shangguan22 分钟前
LeetCode 704 如何正确书写一个二分查找
数据结构·算法·leetcode
兵哥工控34 分钟前
MFC读写文件实例
c++·mfc
Swift社区43 分钟前
【Vue.js 组件化】高效组件管理与自动化实践指南
vue.js·算法·leetcode·职场和发展
快敲啊死鬼1 小时前
代码随想录18
算法
upgrador2 小时前
问卷信效度检验:Cronbach‘s α 与 KMO 值计算详解
算法·matlab
孑么2 小时前
力扣 二叉树的最大深度
java·算法·leetcode·职场和发展·深度优先·广度优先
蒲公英的孩子2 小时前
DCU异构程序——Bank冲突
linux·分布式·算法·架构