1004 Counting Leaves【PAT (Advanced Level) Practice】
原题链接:预览题目详情 - 1004 Counting Leaves (pintia.cn)
1.题目原文
A family hierarchy is usually presented by a pedigree tree. Your job is to count those family members who have no child.
Input Specification:
Each input file contains one test case. Each case starts with a line containing 0 < N < 100 0<N<100 0<N<100, the number of nodes in a tree, and M M M ( < N <N <N), the number of non-leaf nodes. Then M M M lines follow, each in the format:
ID K ID[1] ID[2] ... ID[K]
where ID
is a two-digit number representing a given non-leaf node, K
is the number of its children, followed by a sequence of two-digit ID
’s of its children. For the sake of simplicity, let us fix the root ID to be 01
.
The input ends with N N N being 0. That case must NOT be processed.
Output Specification:
For each test case, you are supposed to count those family members who have no child for every seniority level starting from the root. The numbers must be printed in a line, separated by a space, and there must be no extra space at the end of each line.
The sample case represents a tree with only 2 nodes, where 01
is the root and 02
is its only child. Hence on the root 01
level, there is 0
leaf node; and on the next level, there is 1
leaf node. Then we should output 0 1
in a line.
Sample Input:
2 1
01 1 02
Sample Output:
0 1
2. 题目翻译
家族层次通常由一个家谱树来表示。你的任务是统计那些没有子女的家庭成员的数量。
输入规范:
每个输入文件包含一个测试用例。每个案例都以一行开始,包含 0 < N < 100 0<N<100 0<N<100,树中节点的数量,以及 M M M( < N <N <N),非叶节点的数量。然后,接下来是 M M M 行,每行的格式如下:
ID K ID[1] ID[2] ... ID[K]
其中 ID
是表示给定非叶节点的两位数字,K
是其子节点的数量,后面是其子节点的两位数字 ID
的序列。为了简单起见,我们将根节点的 ID 固定为 01
。
输入以 N N N 为 0 结束。这种情况不准被处理。(就是不用考虑 N N N 为 0 0 0 的情况,前面也说了 0 < N < 100 0<N<100 0<N<100)
输出规范:
对于每个测试用例,你应该从根开始统计那些在每个辈分层次上都没有子女的家庭成员。数字必须以一行的形式打印,用空格分隔,每行末尾不能有额外的空格。
示例案例表示一个只有2个节点的树,其中 01
是根,02
是它唯一的子节点。因此,在根 01
层上,有 0
个叶节点;在下一层上,有 1
个叶节点。然后我们应该输出 0 1
一行。
示例输入:
2 1
01 1 02
示例输出:
0 1
3.解题思路
3.1题目分析
通过输入家谱树结构的描述,统计每个辈分层次上没有子女的家庭成员数量。
输入:
树中节点的数量N 非叶节点的数量M
M行输入:给定非叶节点的两位数字ID(根节点ID固定为 01) 其子节点的数量k 其子节点的两位数字 ID 的序列
输出:
顺次从根节点开始输出每一层没有子节点的结点个数
3.2基本思路
通过构建家谱树并深度优先搜索,统计每个辈分层次上没有子女的家庭成员数量,并按照层次顺序输出结果。
3.3详解步骤
- 树节点结构体定义:
TreeNode
结构体包含三个成员变量,分别是Child
(指向子节点的指针)、name
(节点名称,即两位数字的ID)、Sibling
(指向兄弟节点的指针)。
- 全局变量定义:
Tree T[100]
:用于存储树节点的数组,索引表示节点的ID
。int cnt[99]
:用于存储每个辈分层次上没有子女的家庭成员数量。int level, maxlevel
:用于追踪递归访问树节点时的层次深度。
- 新建树节点函数:
new_node
函数用于创建一个新的树节点,分配内存并初始化节点的Child
和Sibling
指针为NULL
。
- 深度优先搜索访问树节点函数:
Visit
函数用于递归访问树节点,统计每个辈分层次上没有子女的家庭成员数量。- 如果节点没有子女,则增加相应层次的家庭成员数量;否则,递归访问子节点,然后访问兄弟节点。
- 主函数:
- 初始化树节点数组和计数数组。
- 读取输入,构建树结构。
- 输入以
N
和M
开始,分别表示节点数量和非叶节点数量。 - 随后的输入为非叶节点的信息,包括节点
ID
、子节点数量和子节点的ID
。
- 输入以
- 调用
Visit
函数计算每个辈分层次上没有子女的家庭成员数量。 - 输出结果:输出每个辈分层次上没有子女的家庭成员数量,按照层次顺序,用空格分隔。
3.4注意要点
4.参考答案
#include <stdio.h>
#include <malloc.h>
// 定义树节点的结构体
typedef struct TreeNode *Tree;
struct TreeNode {
Tree Child; // 子节点
int name; // 节点名称
Tree Sibling; // 兄弟节点
};
Tree T[100]; // 存储树节点的数组
int cnt[99], level, maxlevel; // cnt数组用于存储每个辈分层次上没有子女的家庭成员数量,level和maxlevel用于追踪层次深度
// 创建新的树节点
Tree new_node() {
Tree temp;
temp = (Tree)malloc(sizeof(struct TreeNode));
temp->Child = temp->Sibling = NULL;
return temp;
}
// 深度优先搜索访问树节点
void Visit(Tree T) {
if (!T->Child)
cnt[level]++; // 如果节点没有子女,增加相应辈分的家庭成员数量
else {
level++; // 进入下一层次
if (level > maxlevel)
maxlevel = level; // 更新最大层次深度
Visit(T->Child); // 递归访问子节点
level--; // 返回上一层次
}
if (T->Sibling)
Visit(T->Sibling); // 递归访问兄弟节点
}
int main() {
int i, j, N, M;
int id1, id2, k;
// 初始化树节点数组和计数数组
for (i = 0; i < 99; i++) {
T[i + 1] = NULL;
cnt[i] = 0;
}
level = maxlevel = 0;
scanf("%d %d", &N, &M);
// 读取输入,构建树
T[1] = new_node();
T[1]->name = 1;
// 读取输入,构建树结构
for (i = 0; i < M; i++) {
scanf("%d %d", &id1, &k);
// 如果当前节点不存在,则创建新节点并初始化
if (!T[id1]) {
T[id1] = new_node();
T[id1]->name = id1;
}
// 如果节点有子节点,读取第一个子节点的信息
if (k) {
scanf("%d", &id2);
// 如果子节点不存在,则创建新节点并初始化
if (!T[id2]) {
T[id2] = new_node();
T[id2]->name = id2;
}
T[id1]->Child = T[id2]; // 设置当前节点的子节点
id1 = id2; // 更新当前节点为子节点,用于处理兄弟节点
}
// 处理当前节点的兄弟节点
for (j = 1; j < k; j++) {
scanf("%d", &id2);
// 如果兄弟节点不存在,则创建新节点并初始化
if (!T[id2]) {
T[id2] = new_node();
T[id2]->name = id2;
}
T[id1]->Sibling = T[id2]; // 设置当前节点的兄弟节点
id1 = id2; // 更新当前节点为兄弟节点,用于处理下一个兄弟节点
}
}
// 计算每个辈分层次上没有子女的家庭成员数量
Visit(T[1]);
// 输出结果
printf("%d", cnt[0]);
for (i = 1; i <= maxlevel; i++)
printf(" %d", cnt[i]);
printf("\n");
// 释放内存
for (i = 0; i < 99; i++) {
free(T[i + 1]);
T[i + 1] = NULL;
cnt[i] = 0;
}
return 0;
}
5.知识拓展
深度优先搜索(Depth-First Search,DFS)
深度优先搜索(Depth-First Search,DFS)是一种用于图和树等数据结构的搜索算法。该算法从起始节点开始,尽可能深地访问每一个可能的分支,直到无法继续为止,然后回溯到上一个节点,继续尝试其他分支。DFS通常使用递归或栈来实现。
以下是深度优先搜索的基本步骤:
-
访问起始节点: 将起始节点标记为已访问,并将其加入访问路径。
-
递归或栈: 对于起始节点的每个未访问邻居节点,重复上述步骤。可以使用递归调用或者使用栈来追踪节点的访问顺序。
-
回溯: 当无法再深入时,回溯到上一个节点,继续尝试其他分支。
-
重复: 重复步骤2和步骤3,直到所有可达节点都被访问过。
深度优先搜索在解决迷宫问题、图遍历、拓扑排序等问题中都有广泛应用。