第五章 树与二叉树总结
Posted yewanting
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第五章 树与二叉树总结相关的知识,希望对你有一定的参考价值。
树结构是一类重要的非线性数据结构
1、树的定义:树(Tree)是n(n>=0)个结点的有限集,它或为空树(n=0);或为非空树;
对于非空树:
(1)有且仅有一个称之为根的结点;
(2)除根结点以外的其余结点可分为m(m>0)个互不相交的有限集T1,T2,...,Tm,其中每个集合本身又是一颗树,并且称为根的子树(SubTree);
2、树的基本术语
(1)结点:树中的一个独立单元。
(2)结点的度:结点拥有的子树数称为结点的度。
(3)树的度:树的度是树内各结点的最大值。
(4)叶子:度为0的结点称为叶子或终端结点。
(5)非终端结点:度不为0的结点称为非终端结点或分支结点。除根结点之外,非终端结点也称为内部结点。
(6)双亲和孩子、(7)兄弟、(8)祖先、(9)子孙
(10)层次:结点的层次从根开始定义起,根为第一层,根的孩子为第二层。
在这章老师布置给我们的作业是:PTA上的一道关于树的遍历,输出叶子的序号;
题目如下:
Given a tree, you are supposed to list all the leaves in the order of top down, and left to right.
Input Specification:
Each input file contains one test case. For each case, the first line gives a positive integer N (≤10) which is the total number of nodes in the tree -- and hence the nodes are numbered from 0 to N−1. Then N lines follow, each corresponds to a node, and gives the indices of the left and right children of the node. If the child does not exist, a "-" will be put at the position. Any pair of children are separated by a space.
Output Specification:
For each test case, print in one line all the leaves‘ indices in the order of top down, and left to right. There must be exactly one space between any adjacent numbers, and no extra space at the end of the line.
Sample Input:
8
1 -
- -
0 -
2 7
- -
- -
5 -
4 6
Sample Output:
4 1 5
解题思路:这道题的一个坑点是会先入为主认为1便是根节点,其实不然。
这道题有个巧妙的地方就是其实题目中没出现的(即不是孩子的序号)便是根结点。
还有这道题要用到队列思想。如何去遍历输出叶子结点。
代码如下:
1 #include<iostream> 2 using namespace std; 3 4 int n ; 5 bool vis[15]; 6 struct node{ 7 int lchild; 8 int rchild; 9 }tree[15]; 10 int sz[15]; 11 int head = 0 , rear = 0; //队列的序号 12 int main() 13 { 14 cin>>n; 15 char l , r; 16 for(int i = 0 ; i < n ;i++) 17 { 18 cin>>l>>r; 19 if(l!=‘-‘) //如果有左孩子 20 { 21 tree[i].lchild = l - ‘0‘; //赋值 22 vis[tree[i].lchild] = 1; //表示该是孩子且访问过; 23 }else 24 { 25 tree[i].lchild = -1; //否则赋值为-1 ,表示没左孩子; 26 } 27 28 if(r!=‘-‘) //如果有右孩子; 29 { 30 tree[i].rchild = r - ‘0‘; //赋值; 31 vis[tree[i].rchild] = 1; //表示是孩子且访问过; 32 }else 33 { 34 tree[i].rchild = -1; //否则赋值为-1,表示没右孩子; 35 } 36 } 37 int root; 38 for(int i = 0 ; i < n ;i++) 39 { 40 if(vis[i]==0) 41 { 42 root = i ; //找出根; 43 break; 44 } 45 } 46 int leaves = 0; 47 sz[rear++] = root; //让根先入队; 48 while(rear - head > 0 ) 49 { 50 int num = sz[head++]; //取出队列的元素 51 if (tree[num].lchild == -1 && tree[num].rchild == -1) { //输出叶节点 52 53 if (leaves) 54 55 printf(" "); 56 57 printf("%d", num); 58 59 ++leaves; 60 61 } 62 63 if (tree[num].lchild != -1) { //如果存在,左儿子入队 64 65 sz[rear++] = tree[num].lchild; //入队; 66 67 } 68 69 if (tree[num].rchild != -1) { //如果存在,右儿子入队 70 71 sz[rear++] = tree[num].rchild;//入队; 72 73 } 74 75 76 } 77 78 }
此外,我们还做了两道实践题,一道是深入虎穴(老师周四在实验室引导我们一步一步去解决出来),老师用的是孩子表示法,而我自己用是vector;
,一道是树的同构;
深入虎穴 题目如下:
著名的王牌间谍 007 需要执行一次任务,获取敌方的机密情报。已知情报藏在一个地下迷宫里,迷宫只有一个入口,里面有很多条通路,每条路通向一扇门。每一扇门背后或者是一个房间,或者又有很多条路,同样是每条路通向一扇门…… 他的手里有一张表格,是其他间谍帮他收集到的情报,他们记下了每扇门的编号,以及这扇门背后的每一条通路所到达的门的编号。007 发现不存在两条路通向同一扇门。
内线告诉他,情报就藏在迷宫的最深处。但是这个迷宫太大了,他需要你的帮助 —— 请编程帮他找出距离入口最远的那扇门。
输入格式:
输入首先在一行中给出正整数 N(<10?5??),是门的数量。最后 N 行,第 i 行(1≤i≤N)按以下格式描述编号为 i 的那扇门背后能通向的门:
K D[1] D[2] ... D[K]
其中 K
是通道的数量,其后是每扇门的编号。
输出格式:
在一行中输出距离入口最远的那扇门的编号。题目保证这样的结果是唯一的。
输入样例:
13
3 2 3 4
2 5 6
1 7
1 8
1 9
0
2 11 10
1 13
0
0
1 12
0
0
输出样例:
12
解题思路:这道题就是说给你N个数,代表N个结点,第i行,每行有K个数;
让你找树深度最深的那个结点;题目注意要先找出根,其实题目没出现的数字就是根,所以我
们可以把孩子都标记起来,没有被标记的就是根;我们可以先用一个vector把孩子都放在共同的父亲上,
然后孩子的深度都比父亲多一;代码如下:
代码如下:
1 #include<iostream> 2 #include<vector> 3 #include<queue> 4 using namespace std; 5 6 int N; 7 int K; 8 int tmp; 9 bool vis[100005];//用来标记点是否出现过,方便找出根结点; 10 vector<int>v[100005]; //动态数组,将相同父亲的孩子放入父亲的vector中; 11 int depth[100005]; //用来记录每个数字的深度; 12 int root; 13 int tp; 14 int maxnn = -1; //用来找深度最大的; 15 int ans = -1; //用来记录答案; 16 int main() 17 { 18 queue<int>q; 19 scanf("%d",&N); 20 for(int i = 1 ; i <= N ;i++) 21 { 22 scanf("%d",&K); 23 while(K--) 24 { 25 scanf("%d",&tmp); 26 v[i].push_back(tmp); //将孩子结点放入父亲的数组中; 27 vis[tmp] = 1; //标记该点是孩子结点; 28 } 29 } 30 31 for(int j = 1 ; j <= N ;j++) 32 { 33 if(vis[j]!=1) 34 { 35 root = j ; //找到根结点; 36 } 37 } 38 q.push(root); //将根结点入队; 39 depth[root] = 1; //根的深度为1; 40 while(!q.empty()) 41 { 42 tp = q.front(); 43 q.pop(); 44 for(int j = 0 ; j < v[tp].size();j++) 45 { 46 depth[v[tp][j]] = depth[tp]+1; //每个孩子的深度比父亲多一; 47 q.push(v[tp][j]); //将孩子入队; 48 } 49 } 50 51 for(int j = 1 ; j <= N ;j++) 52 { 53 if(depth[j]>maxnn) 54 { 55 maxnn = depth[j]; //找最大深度的; 56 ans = j; //找到答案; 57 } 58 } 59 printf("%d\n",ans); 60 61 return 0; 62 }
另一道是树的同构,树的同构是一个全新的概念,一开始是不知道树的同构是什么意思,觉得很复杂,不知道如何下手,但是静下心来想,其实它可以分为如下几种情况:
假设它同构:
1)两颗都为空树;
2)只有根,根的数值想等;
3)两颗树对应的左孩子相等,右孩子相等;
4)两颗树对应的左孩子等于右孩子;
其余情况都不符合;
题目如下:
输入格式:
输入给出2棵二叉树树的信息。对于每棵树,首先在一行中给出一个非负整数N (≤10),即该树的结点数(此时假设结点从0到N−1编号);随后N行,第i行对应编号第i个结点,给出该结点中存储的1个英文大写字母、其左孩子结点的编号、右孩子结点的编号。如果孩子结点为空,则在相应位置上给出“-”。给出的数据间用一个空格分隔。注意:题目保证每个结点中存储的字母是不同的。
输出格式:
如果两棵树是同构的,输出“Yes”,否则输出“No”。
输入样例1(对应图1):
8
A 1 2
B 3 4
C 5 -
D - -
E 6 -
G 7 -
F - -
H - -
8
G - 4
B 7 6
F - -
A 5 1
H - -
C 0 -
D - -
E 2 -
输出样例1:
Yes
输入样例2(对应图2):
8
B 5 7
F - -
A 0 3
C 6 -
H - -
D - -
G 4 -
E 1 -
8
D 6 -
B 5 -
E - -
H - -
C 0 2
G - 3
F - -
A 1 4
输出样例2:
No
代码如下:
1 #include<iostream> 2 #include<stdio.h> 3 #include<string.h> 4 using namespace std; 5 6 7 struct node{ 8 char data; 9 int l; 10 int r; 11 }tree1[10],tree2[10];//定义树的结构体; 12 int root1 , root2; 13 int vis[10];//作为标记,方便找到根; 14 int creat_tree( node tree[]) 15 { 16 int root = -1; //先将根标记为-1; 17 char data , l , r; 18 int n ; 19 scanf("%d",&n); 20 getchar(); 21 for(int i = 0 ; i < n ;i++) 22 { 23 vis[i] = 0; //先将所有标记初始化为0; 24 } 25 for(int i = 0 ; i < n ; i++) 26 { 27 scanf("%c %c %c",&data,&l,&r); 28 tree[i].data = data; 29 if(l == ‘-‘) 30 tree[i].l = -1; //如果没有左孩子,则标记为-1; 31 else 32 { 33 tree[i].l = l -‘0‘; //有左孩子,则赋值, 34 vis[tree[i].l] = 1; //且标记为访问过; 35 } 36 if(r==‘-‘) //如果没有右孩子; 37 tree[i].r = -1; //标记右孩子为-1; 38 else 39 { 40 tree[i].r = r - ‘0‘; //有右孩子,则赋值, 41 vis[tree[i].r] = 1; //且标记为访问过; 42 } 43 getchar(); 44 } 45 for(int i = 0 ; i < n ; i++) 46 { 47 if(vis[i]==0) 48 { 49 root = i; //如果有根,则赋值;(如果在这里找不到根,则root==-1) 50 } 51 } 52 return root; 53 54 } 55 int Istonggou(int root1 ,int root2) 56 { 57 if(root1 == -1 &&root2 ==-1) //两颗都为空树,满足同构; 58 { 59 return 1; 60 } 61 else 62 if( root1 == -1 && root2 != -1) return 0; //一颗为空树,一颗不为空树,不满足 63 else 64 if( root1 != -1 && root2 == -1) return 0; //一颗为空树,一颗不为空树,不满足 65 else 66 if(tree1[root1].data!=tree2[root2].data) //两颗树根的数值不同;不满足; 67 { 68 return 0; 69 } 70 //其余情况其实总体来说就分为两大类 71 //1)(1)tree1的左子树和tree2的左子树相同; (2)tree1的右子树和tree2的右子树相同; 72 //2)(1)tree1的左子树和tree2的右子树相同;(2)tree1的右子树和tree2的左子树相同; 73 return (Istonggou(tree1[root1].l,tree2[root2].l)&&Istonggou(tree1[root1].r,tree2[root2].r))|| 74 (Istonggou(tree1[root1].l,tree2[root2].r)&&Istonggou(tree1[root1].r,tree2[root2].l)); 75 } 76 77 int main() 78 { 79 int flag = 0; 80 root1 = creat_tree(tree1); 81 root2 = creat_tree(tree2); 82 flag = Istonggou(root1,root2); 83 if(flag) //如果满足同构树的条件; 84 printf("Yes\n"); 85 else 86 printf("No\n"); 87 return 0; 88 }
其实除了这几道题外,还学习了树的相关性质,二叉树的性质(挺重要的,这里便不赘述了)
二叉树存储方式:顺序存储和链式存储(二叉链表,三叉链表)
二叉树几种表示方法:如孩子表示法,双亲表示法,孩子双亲表示法
还学习了哈夫曼树,其实哈夫曼树是一个很重要的东西,更重要的是其思想。原本想尝试着在这篇博客中解释哈夫曼树,(其之前建的树不一样,之前的树都是从上往下建,而哈夫曼树是从下往上建,这样建也是有其原因)。但是写得有点匆忙,等后期补充吧。
这一章的学习:其实感觉学的不是那么扎实,感觉自己对树的很多操作还是不是那么熟,不是那么能灵活运用,感觉还有待提高。这周也课外学了线段树和树状数组(这其实是寒假的时候师兄教的内容了,但现在重新学牢),感觉树还是有很多便捷之处。
下一章的学习是图,其实图是很重要的,很多人对图论的知识也是不是那么的熟悉,对于图我也只懂得最小生成树和最短路径,希望在老师的讲解下,能灵活运用!
以上是关于第五章 树与二叉树总结的主要内容,如果未能解决你的问题,请参考以下文章
(王道408考研数据结构)第五章树-第三节4:树与二叉树的转换