题目来源:
中国大学MOOC-陈越、何钦铭-数据结构
题目详情:
译文:
给定一棵树,按照从上到下,从左到右的顺序列出所有叶结点。
输入格式:
每个输入文件包含一个测试用例。 对于每种情况,第一行给出一个正整数
N
(
≤
10
)
N(≤10)
N(≤10),它是树中节点的总数。节点从
0
0
0到
N
−
1
N-1
N−1编号。 然后跟随
N
N
N行,每行对应一个节点,并给出该节点左右子节点的索引。 如果孩子不存在,将在该位置放置一个“-
”。 任何一对孩子都用空格隔开。
输出格式:
对于每个测试用例,按从上到下,从左到右的顺序在一行中打印所有叶结点的索引。 相邻数字之间必须恰好有一个空格,行尾不得有多余的空格。
输入样例:
8
1 -
- -
0 -
2 7
- -
- -
5 -
4 6
输出样例:
4 1 5
解题思路:静态链表建树+层序遍历输出
关于本题,整体来看大致分以下两个步骤:
- 表示二叉树
- 创建二叉树
- 遍历二叉树输出叶结点
接下来便一步步的分析。
-
表示二叉树
由于本题的输入与上一篇博文中介绍题目的输入有异曲同工之处,因此表示二叉树和创建二叉树的方法可以借鉴上一题,即用静态链表表示。
二叉树的常用表示方法是用动态链表来表示,如下图:
value代表该节点的值,left和right为指向左子树和右子树的指针。
但是除此之外二叉树还有一种表示方法,那就是静态链表。所谓静态链表,,就是用数组来实现链式存储结构。对于本题这种树的结点数已经确定,输入是可以无序的情况十分使用,拿输入样例来举例,相应的静态链表如下图:
其中最上面的一排代表数组的下标,left
代表该结点对应左子树结点在数组中的下标,right
代表该结点对应右子树结点在数组中的下标。-1
代表相应的子树为空。
用该方法的一个好处就是可以很容易地从无序的输入确定根节点是谁。可以发现,该数组的长度为8,因此下标是0~7,从头遍历图中数组,可以发现0~7这些下标中,3没有出现,因此说明没有任何一个结点的子树指向下标3对应的结点,即下标为3对应的结点为该树的根节点,即图中的下标3对应的结点3。 -
建立二叉树
明确了用静态链表来表示二叉树,接下来就是建树了,关键的一步就是我们要找到树的根结点,其他的直接看后面程序中的注释应该就能理解。找到树的根结点,即数组中哪个下标没有出现过,因此考虑维护一个数组flag[N]
用来记录哪些下标出现过,N
为数组输入结点的总数,数组的初始值全为0,在遍历数组建树的过程中,每出现一个下标就将该下标对应的位置置1,然后看看哪个位置的值最后为0,那么就明确了头结点。
按步骤1的静态链表我们可以得到该输入对应的二叉树为下图:
可以发现该树的叶结点按从上到下,从左到右的顺序在一行中打印的结果确实为输出样例中的 415 4 1 5 415,因此可以认为我们这个表示和建树方法是行得通的。 -
遍历二叉树输出叶结点
二叉树常见的遍历方法有深度优先遍历(DFS)的前序遍历,中序遍历,后续遍历和广度优先遍历(BFS)的层序遍历。
若我们用DFS来遍历该树,则不管是用什么序遍历,都会先输出结点1
,因为结点1
的深度最大,但是题目要求我们按从上到下,从左到右的顺序来输出,因此很自然的想到用BFS,这样就会先输出深度不是最大的结点4
。BFS的实现思想就是采用一个队列,从第一层开始,从左至右将结点入队,结束后再将队中元素逐个出队,每出队一个元素都查看它是否有左子树和右子树,有的话将其左子树和右子树入队,如此便可以实现每层的遍历。具体的实现见程序。
完整程序如下:
#include <stdio.h>
#define MAX_SIZE 10 //输入数据的最大长度
struct TreeNode{ //树的静态链表表示
int val;
int left;
int right;
}T[MAX_SIZE];
typedef struct TreeNode Tree;
void bulidTree(Tree T[]);
void printLeaves(int R, int N, int count, Tree T[], int flag[]);
int main(){
bulidTree(T);
return 0;
}
void bulidTree(Tree T[]){
int i, N, count = 0, R = -1;
char temp_left, temp_right;
scanf("%d", &N);
int flag[N];
if(N){
for(i=0; i<N; i++)
flag[i] = 0; //将标志位全置0
for(i=0; i<N; i++){
T[i].val = i;
scanf("\n%c %c", &temp_left, &temp_right);
if(temp_left == '-' && temp_right == '-')
count++; //统计叶结点的个数打印时使用
if(temp_left != '-'){ //左子树不为空
T[i].left = temp_left - '0';
flag[T[i].left] = 1;
}
else
T[i].left = -1;
if(temp_right != '-'){ //右子树不为空
T[i].right = temp_right - '0';
flag[T[i].right] = 1;
}
else
T[i].right = -1;
}
}
printLeaves(R, N, count, T, flag);
}
void printLeaves(int R, int N, int count, Tree T[], int flag[]){
int i, queue[MAX_SIZE] = {0}, head = -1, tail = -1; //初始化层序遍历的队列
Tree temp_node;
for(i=0; i<N; i++)
if(!flag[i]){
queue[++tail] = i; //找到根节点并入队
break;
}
while(head != tail){ //判断队列是否位空
temp_node = T[queue[++head]];
if(temp_node.left != -1)
queue[++tail] = temp_node.left; //将左子树入队
if(temp_node.right != -1)
queue[++tail] = temp_node.right; //将右子树入队
if(temp_node.left == -1 && temp_node.right == -1){
printf("%d", temp_node.val); //输出叶结点
if(--count)
printf(" "); //若不是最后一个叶结点则输出space
}
}
}
如果有哪里理解错的地方欢迎大家留言交流,如需转载请标明出处。
如果你没看懂一定是我讲的不好,欢迎留言,我继续努力。
手工码字码图码代码,如果有帮助到你的话留个赞吧,谢谢。
以上。