洛谷B3644 - 【模板】拓扑排序 / 家谱树
题目描述
有个人的家族很大,辈分关系很混乱,请你帮整理一下这种关系。给出每个人的后代的信息。输出一个序列,使得每个人的后辈都比那个人后列出。
输入格式
第 1 1 1 行一个整数 N N N( 1 ≤ N ≤ 100 1 \le N \le 100 1≤N≤100),表示家族的人数。接下来 N N N 行,第 i i i 行描述第 i i i 个人的后代编号 a i , j a_{i,j} ai,j,表示 a i , j a_{i,j} ai,j 是 i i i 的后代。每行最后是 0 0 0 表示描述完毕。
输出格式
输出一个序列,使得每个人的后辈都比那个人后列出。如果有多种不同的序列,输出任意一种即可。
样例 #1
样例输入 #1
5
0
4 5 1 0
1 0
5 3 0
3 0
样例输出 #1
2 4 5 3 1
这题非常非常简单,就是一个拓扑排序模板题。
题目直接告诉你每个人的后代,其实就是每个顶点的直接后继,然后求这个图的拓扑排序序列。
关于拓扑排序的详细讲解,请看我这篇博客:图的应用3-有向无环图、拓扑排序
我一共写了三种做法,全都 A C AC AC,足见其简单,而且这题数据范围也很友好,邻接矩阵可以放心食用。
首先是邻接矩阵实现,完整代码如下:
//AC(100pts)
#define _CRT_SECURE_NO_WARNINGS 1
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;const int N = 114;
int n, indegree[N], print[N];//顶点数,各点入度,拓扑序列数组
bool A[N][N];//邻接矩阵存图inline void Init() {cin >> n;for (int i = 0; i <= n; ++i) {//初始化indegree[i] = 0;for (int j = 0; j <= n; ++j) {A[i][j] = false;}}for (int i = 1, j; i <= n; ++i) {for (int k = 0; k <= n; ++k) {cin >> j;if (!j)break;//读到0结束当前顶点的后代读入A[i][j] = true;//邻接矩阵加入有向边indegree[j]++;//后代的入度加1}}return;
}inline bool TopologicalSort() {//拓扑排序int Sta[N], top = 0, count = 0;//静态数组模拟栈memset(Sta, 0, sizeof(Sta));//初始化栈int i;for (i = 1; i <= n; ++i)if (!indegree[i])Sta[++top] = i;//入度为0的顶点入栈while (top) {i = Sta[top--];//栈顶元素出栈print[++count] = i;//输出ifor (int j = 1; j <= n; ++j)if (A[i][j]) {//若i到j直接存在一条边A[i][j] = false, indegree[j]--;//删除该边,j的入度减1if (!indegree[j])Sta[++top] = j;//若j的入度减为0,则入栈}}if (count < n)return false;//若计数小于顶点数,说明存在环else return true;//否则拓扑排序成功
}int main() {Init();if (!TopologicalSort())cout << "Error!" << endl;//排序失败else {//排序成功for (int i = 1; i <= n; ++i)cout << print[i] << " ";//输出拓扑排序序列cout << endl;}return 0;
}
邻接矩阵的完整代码也可看我的Github:传送门
( ↑ ↑ ↑ 内附赠逆拓扑排序实现代码)
然后是邻接表实现,之前我贴的讲解博客里有各函数的拆分讲解。
完整代码如下:
//AC(100pts)
#define _CRT_SECURE_NO_WARNINGS 1
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;#define MaxVertexNum 114
typedef struct ArcNode {int adjvex;struct ArcNode* nextarc;
}ArcNode, * ArcList;
typedef struct VNode {int data;ArcNode* firstarc;
}VNode, AdjList[MaxVertexNum];
typedef struct {AdjList vertices;int vexnum, arcnum;
}ALGraph;int indegree[MaxVertexNum], print[MaxVertexNum];
//记录各点入度,记录拓扑排序序列inline void Init(ALGraph& G) {//预处理for (int i = 1; i <= G.vexnum; ++i) {ArcList h = (ArcList)malloc(sizeof(ArcList));h->adjvex = 0;h->nextarc = NULL;G.vertices[i].data = 0;G.vertices[i].firstarc = h;}memset(indegree, 0, sizeof(indegree));memset(print, 0, sizeof(print));return;
}inline void AG_Insert(ALGraph& G, int i, int j) {//头插法加入边ArcNode* p = (ArcNode*)malloc(sizeof(ArcNode));p->adjvex = j;ArcNode* head = G.vertices[i].firstarc;ArcNode* tail = G.vertices[i].firstarc->nextarc;p->nextarc = tail;head->nextarc = p;return;
}inline bool Topologicalsort(ALGraph G) {int Sta[MaxVertexNum], top = 0, count = 0;//用静态数组模拟栈,top为栈顶指针,count为拓扑序列数组下表memset(Sta, 0, sizeof(Sta));int i;for (i = 1; i <= G.vexnum; ++i)if (!indegree[i])Sta[++top] = i;//将初始入度为0的顶点进栈while (top) {//当栈不空时i = Sta[top--];//栈顶元素出栈print[++count] = i;//输出顶点ifor (ArcNode* p = G.vertices[i].firstarc; p != NULL; p = p->nextarc) {//将所有i指向的顶点入度减1,并将入度减为0的顶点压入栈Sta中int v = p->adjvex;if (!v)continue;if (!(--indegree[v]))Sta[++top] = v;//度为0则入栈p->adjvex = 0;//删除边}}if (count < G.vexnum)return false;//排序失败,图中存在回路else return true;//拓扑排序成功
}int main() {ALGraph G;cin >> G.vexnum;Init(G);for (int i = 1, j; i <= G.vexnum; ++i) {for (int k = 0; k <= G.vexnum; ++k) {cin >> j;if (!j)break;AG_Insert(G, i, j);indegree[j]++;}}if (!Topologicalsort(G))cout << "No DAG!" << endl;else {for (int i = 1; i <= G.vexnum; ++i)cout << print[i] << " ";cout << endl;}return 0;
}
最后是邻接表的 D F S DFS DFS实现,预处理和基本操作函数之类的直接用上面的邻接表实现,原理详解请看之前我贴的讲解博客。
完整代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;#define MaxVertexNum 114
typedef struct ArcNode {int adjvex;struct ArcNode* nextarc;
}ArcNode, * ArcList;
typedef struct VNode {int data;ArcNode* firstarc;
}VNode, AdjList[MaxVertexNum];
typedef struct {AdjList vertices;int vexnum, arcnum;
}ALGraph;int indegree[MaxVertexNum], print[MaxVertexNum];
//记录各点入度,记录拓扑排序序列inline void Init(ALGraph& G) {//预处理for (int i = 1; i <= G.vexnum; ++i) {ArcList h = (ArcList)malloc(sizeof(ArcList));h->adjvex = 0;h->nextarc = NULL;G.vertices[i].data = 0;G.vertices[i].firstarc = h;}memset(indegree, 0, sizeof(indegree));memset(print, 0, sizeof(print));return;
}inline void AG_Insert(ALGraph& G, int i, int j) {//头插法加入边ArcNode* p = (ArcNode*)malloc(sizeof(ArcNode));p->adjvex = j;ArcNode* head = G.vertices[i].firstarc;ArcNode* tail = G.vertices[i].firstarc->nextarc;p->nextarc = tail;head->nextarc = p;return;
}int tim, finishtime[MaxVertexNum];//计时变量,各顶点用时数组
bool visited[MaxVertexNum];//访问标记inline void DFS(ALGraph G, int v) {visited[v] = true;for (ArcNode* p = G.vertices[v].firstarc->nextarc; p != NULL; p = p->nextarc) {int w = p->adjvex;//依次遍历当前顶点的邻边未访问的顶点if (!visited[w])DFS(G, w);}finishtime[v] = ++tim;//搜索深度越深,tim值越小//如果要输出逆拓扑排序序列,只需把这一行改为输出v即可return;
}inline void DFSTravere(ALGraph G) {memset(visited, false, sizeof(visited));//初始化memset(finishtime, 0, sizeof(finishtime));tim = 0;for (int i = 1; i <= G.vexnum; ++i)//从第一个顶点开始深搜if (!visited[i])DFS(G, i);return;
}int main() {ALGraph G;cin >> G.vexnum;Init(G);for (int i = 1, j; i <= G.vexnum; ++i) {for (int k = 0; k <= G.vexnum; ++k) {cin >> j;if (!j)break;AG_Insert(G, i, j);indegree[j]++;}}DFSTravere(G);//深搜int con = 0;//计数for (int i = 1; i <= G.vexnum; ++i) {int maxn = -1, maxm = 0;for (int j = 1; j <= G.vexnum; ++j)if (finishtime[j] > maxn)maxn = finishtime[j], maxm = j;//每次循环找到finishtime数组里tim值最大的顶点finishtime[maxm] = -1;//删除当前tim的最大值print[++con] = maxm;//输出当前tim值最大的顶点}for (int i = 1; i <= G.vexnum; ++i)cout << print[i] << " ";//输出拓扑排序序列cout << endl;return 0;
}
邻接表的两个实现完整代码也可以看我的Github:传送门
不过我没写判环操作,话说有环的话那就不是 D A G DAG DAG图了……
总之还是非常简单的一题,但是有点费时间了。
以上。