分享内容
- 树的定义
- 树的表示和存储
- 树的遍历
1.树的定义和概念
数据结构
数据结构可分为"线性"和"非线性"两大类:

树(数据结构)
自然界的一棵树有树根、有树杈、有叶子。
将这棵树上下翻转,就是数据结构中常见的树结构了。

树的定义
一棵树是由n(n≥0)个元素组成的有限集合。
n=0,表示空树


一棵树是由n(n≥0)个元素组成的有限集合。
① 组成树的每个元素称为结点(node),或节点。
② 连接两个结点的线段称为边。
③任意一棵非空树中(n>0),有一个特定的结点,称为根结点或树根(root)。
④根结点有且仅有一个。
⑤除了根结点外,其他每个结点都只有一个前驱结点;而每个结点的后继结点数是不确定的。
因此树结构是非线性结构。

树结构的基本概念
树结构中用到的专业名词较多,因此以下图的树结构为例,介绍相关概念。
- 父结点和子结点:
父结点是某结点的前驱结点,而子结点则是某结点的后继结点。
例如,3是6的父结点,7、8是6的子结点。 - 兄弟结点:父结点相同的结点。
例如,4、5、6的父结点都是3,因此这三个结点互为兄弟结点。

- 祖先结点:
一个结点的父结点和父结点的祖先。或者说,一个结点到根结点的路径上,除了它
本身外的所有结点。 - 后代结点:一个结点的子结点和子结点的后代。
例如,图片中的结点6的祖先结点有1、3。
结点6的后代结点有7、8、9、10。
5. 叶子结点:子结点数为0的结点。即最末端的结点。
例如,图中的绿色的都是叶子结点。

- 子树:是指以某个结点为根的树的一部分。具体来说,对于树中的任意一个结点N,
以N为根的树称为N的子树。子树包括N本身以及N的所有后代结点。
例如图中:

树的基本概念(总结)

练一练1
下图的树结构中,根结点是a。叶子结点的个数_5,它们是degij 。结点f的兄弟结点是eg

树的度
结点的度:指的是结点的子结点个数。
例如,图中结点a的度是2,结点b的度是1,
结点c的度是3。其他都是叶结点,度为0。
树的度:树中所有结点的度的最大值就是树的度。
图树的度为3。因为树中结点c的度最大,为3。

树的深度
结点的深度:是指从根结点到该结点所经过的边的数量。
例如,右图结点f的深度是2,结点i的深度是4。
树的深度:是指从根结点到最远叶结点所经过的边的数量。
例如,右图树的深度为4。因为树中i、j是离根最远的
叶结点,它们的深度都是4。

【注意】我们通常将"深度"定义为"经过的边的数量",但有些题目或教材可能会将其定义为
"经过的结点的数量"。在这种情况下,深度需要加1。
练一练
下图树结构中,树的度是3,树的深度是_5?("深度"定义为"经过的边的数量")

树的表示和存储
树的表示
树结构一般有三种表示法:
(1)父亲表示法(双亲表示法)
(2)孩子表示法
(3)孩子兄弟表示法
每种表示方法都有其特点和适用场景。
· 如何用这三种表示法存储这棵树?
· 各有什么特点?
树的父亲表示法
(在树结构中,除根结点外,每个结点都只有一个父结点)
父亲表示法(双亲表示法):通过记录每个结点的父结点来表示一棵树。
1、树以结点为单位,先定义结点(结构体):
cpp
struct tree node{
int parent;//父结点的编号
int data;//结点保存的数值
每个结点包含数据和父结点的编号
};
2、定义结点数组:(存储树的所有结点)
cpp
tree node tree[maxn];//树

3、将树的结点依次编号。(编号从1开始)
4、再将结点数据存放到数组对应的位置。(编号i的结点信息存放在treei)
cpp
tree [1] .parent = 0;
tree [2] .parent = 1;
tree [3] .parent = 1;
tree [4] .parent = 1;
tree [5] .parent = 2;
tree [6] .parent = 2;
tree [7] .parent = 3;

· 根结点没有父结点,因此其父结点的下标存放的值为0或-1
· 其他的结点存储对应父结点的下标

这棵树就保存好了!


父亲表示法的优点:能很方便地找到每个结点的前驱(祖先结点)。
例如,结点2的父结点(的编号):
cpp
cout << tree[2].parent << endl;
例如,结点5的所有祖先结点:
cpp
tree node n = tree [5];
while (n.parent != 0) {
cout << n.parent << endl;
n = tree [n. parent] ;
}
父亲表示法的缺点:无法快速找到结点的后继(后代)以及兄弟结点。

树的孩子表示法
(在树结构中,除了叶子结点外,每个结点都有一个或多个子树)
孩子表示法:通过记录每个结点的孩子结点来表示一棵树。
1、定义结点的结构体:
cpp
//子结点可以有多个,所以定义成数组
struct tree node {
int son[maxn];//子结点数组
int data;//本结点保存的数值
};
2、定义结点数组:
cpp
tree node tree[maxn];//树

3、建立结点间的连接(为每个结点赋值)
cpp
//结点1有3个孩子结点2、3、4
tree [1] . son [1] = 2;
tree [1] .son [2]= 3;
tree [1] . son [3] = 4;
//结点2有2个孩子结点5、6
tree [2] .son [1] = 5;
tree [2] .son [2] = 6;
//结点7是叶结点,无孩子结点
tree [7] .son [1] = 0;

· 叶子结点没有对应的孩子结点,因此存放的值为0或-1。
· 其他的结点存储对应的孩子结点下标。

孩子表示法的优点:能很方便地找到每个结点的后继(后代结点)。
例如,访问结点2的所有子结点:
cpp
for(int i=1;i<maxn; i++) {
if (tree [2] .son [i] != 0)
cout << tree[2].son[i] << endl;
}

孩子表示法的缺点:无法快速找到结点的前驱(祖先结点)以及兄弟结点。
例如,查找兄弟结点需要先遍历找到父结点,然后遍历父节点的孩子列表。
树的孩子兄弟表示法
孩子兄弟表示法:为每个结点保存第一个孩子结点和右边第一个兄弟。
cpp
struct tree node {
int firstSon;//(左边)第一个孩子结点
int right;//右边第一个兄弟结点
int data;//本结点保存的数值
};
tree node tree [maxn] ; //#x


孩子兄弟表示法的优点:能很方便地找到每个结点的后继(后代结点)。
访问子结点的方法:找到第一个孩子结点,然后通过他的right逐个查找兄弟结点。
例如,访问结点1的所有子结点:
cpp
// 找到结点1的第一个孩子结点
int sonID = tree[1].firstSon;//即结点2
// 从结点2的right逐个找到其兄弟结点
while (sonID != 0) {
cout << sonID << endl;
tree node n = tree [sonID] ;
sonID = n.right;
};

孩子兄弟表示法可以访问兄弟结点,但是不是很高效。
访问兄弟结点的方法:先找到父结点,然后从第一个孩子结点开始通过right逐个查找兄弟结点。
例如,访问结点3的所有兄弟结点:
cpp
(父结点需要通过遍历找到,省略)
// 先找到3的父结点,即结点1
..
// 结点1的第一个子节点是结点2
int sonID = tree[1].firstSon;//即结点2
/ 从结点2的right逐个找到兄弟结点
do {
if(sonID != 3)//去掉自身
cout << sonID<< endl;
sonID = tree [sonID].right;
}while (sonID != 0) ;

树的其他表示方法
如果有需要,我们也可以在孩子兄弟表示法基础上为每个结点的保存父结点,从而便
于树结构的遍历。
但是如果每个结点都记录下对应的孩子结点、兄弟结点和父结点,占用的存储空间就
会很多,反而降低了数据结构的使用效率。
因此,存储结构的设计是一个灵活的过程,要根据具体的需求、时空复杂度的限制来
灵活变动。
树的表示法比较
选择哪种表示方法取决于具体的应用需求。

树的遍历
树型结构的通用的遍历方式有三种:
先访问根结点,所以叫"先序"
(1)先序遍历:先访问根结点,再从左往右依次递归遍历各子树。
右图的先序遍历结果为:ABEFCGD。
后访问根结点,所以叫"后序"
(2)后序遍历:先从左往后依次遍历各个子树,再访问根结点。
右图的后序遍历结果为:EFBGCDA。
逐层访问,所以叫"层序"
(3)层序遍历:按照第1层、第2层...的顺序逐层访问,同一层按从
左往右的顺序遍历。
右图的层序遍历结果为:ABCDEFG

访问祖先
指定下图所示的树结构,圆圈内是树结点的值。请你编程保存这个树的信息,然后输出d、i、g结点的祖先结点(的值)。输出3行,每个结点的祖先占一行,祖先按近到远排列,之间以空格隔开。
输入样例无
【输出样例】
b a
hfca
c a

本题首先要求保存指定的树结构,并要求输出指定结点的祖先。
树的三种表示方式中,父亲表示法可以方便地访问祖先,所以我们选择父亲表示法。
1、先定义结点(结构体):
cpp
struct tree node {
int parent;//父结点的编号
char data;//结点保存的数值(本题结点数据是字符型)
};
2、定义结点数组:
cpp
tree node tree[12];//树----推荐定义成全局变量
3、将树的结点依次编号。
编号从1开始,可以按从上到下、从左到右的顺序编号,或者按先序遍历顺序编号。只要保证编号连续且不重复。

4、再将结点数据存放到数组对应的位置。
对应右图 -- 赋值。编号的结点存放在treei
【注意】不要出错、遗漏。
cpp
tree [1] .parent = 0;
tree [1] .data='a' ;
tree [2] .parent = 1;
tree [2] .data='b' ;
tree [3] .parent = 2;
tree [3] .data = 'd';

5、输出d、i、g结点的祖先结点(的值)
先遍历找到d、i、g结点,再根据parent逐级往上找,直到根结点。
cpp
for(int i=1;i <= 10;i++) {
if (tree [i] .data == 'd' | | tree [i] . data == ' i' | | tree [i] . data == 'g' ) {
tree node n = tree [i];
while (n.parent != 0) {//parent != 0,说明有父结点
n = tree[n.parent];//祖先结点
cout << n.data << "";//祖先结点的值-----输出祖先结点的值而不是编号
}
cout << endl;
}
}
完整代码
cpp
#include<iostream>
using namespace std;
#define maxn 100
struct tree node{
int parent;//父结点的编号
char data; //节点保存的数值
};
tree_node tree[maxn]; //树
int main() {
//保存树结构
tree [1] .parent = 0;
tree [1] .data='a';
tree[2] .parent = 1;
tree [2] .data='b';
tree [3] .parent = 2;
tree [3].data = 'd';
tree [4] .parent = 1;
tree [4] .data = 'c';
tree [5] .parent = 4;
tree [5] .data = 'e';
tree [6] .parent = 4;
tree [6] .data = 'f' ;
tree [7] .parent = 6;
tree [7] .data = 'h';
tree [8] .parent = 7;
tree [8] .data = 'i';
tree [9] .parent = 7;
tree [9] .data = 'j';
tree [10] .parent = 4;
tree [10].data = 'g';
//输出d、i、g结点的祖先结点
for(int i=1;i <= 10;i++) {
if(tree[i].data == 'd' | |tree[i].data == 'i'l|tree[i].data == 'g') {
tree node n = tree[i];
while (n.parent != 0) {
n=tree[n.parent];//祖先结点
cout << n.data << "";//输出祖先结点的值
}
cout << endl;
}
}
return 0;
}
访问子节点
指定下图所示的树结构,圆圈内是树结点的值。请你编程保存这个树的信息,然后输出b、c、f、h结点的子结点(的值)。输出4行,每个结点的子结点占一行。有多个子结点时,从左到右排列,之间以空格隔开;没有子节点的输出"LEAF"。
【输入样例】无
【输出样例】
d
e f g
h
LEAF

本题首先要求保存指定的树结构,并要求输出指定结点的孩子结点。
孩子表示法可以方便地子结点,所以我们选择孩子表示法。
1、先定义结点(结构体):
cpp
struct tree node {
int son[maxn];//孩子结点(编号)数组
char data;//结点保存的数值
};
2、定义结点数组:
cpp
tree node tree[12];//树
3、将树的结点依次编号。
编号从1开始,保证编号连续且不重复。
4、再将结点数据存放到数组对应的位置。
多个子结点按从左到右填入son\[\],这样方便按序遍历子结点。
cpp
tree [1] .data='a' ;
tree [1] . son [1] = 2;//1有2个子节点
tree [1] . son [2] = 4;
tree [2] .data='b' ;
tree [2] .son [1] = 3;//2有1个子节点
tree [3] .data='d' ;//3没有子节点
其他son元素默认初始值为0

5、输出b、c、f、h结点的子结点(的值)
先遍历找到b、c、f、h结点,再根据son\[\]找到所有子节点。需要处理叶子结点的输出。
cpp
for(int i=1;i<maxn; i++) {
if (tree[i] .data == 'b' | |tree [i] . data == 'c' | | tree [i] . data == 'f' | |tree [i] .data == 'h') {
tree node n = tree [i];
bool flag = false;
for(int j=1;j<maxn; j++) {
if(n.son[j] != 0){//子结点存在
cout << tree [n . son [j]] .data << " ";//输出子结点的值而不是编号
flag = true;
}
if(! flag)
cout << "LEAF";//处理叶子节点的情况
cout << endl;
}
}
完整代码
cpp
#include<iostream>
using namespace std;
#define maxn 10
struct tree node{
int son[maxn];//孩子结点数组
char data;//节点保存的数值
};
tree node tree[maxn]; //
int main () {
//保存树结构
tree [1] . data='a';
tree [1] . son [1] = 2;
tree [1] . son [2] = 4;
tree [2] .data='b' ;
tree [2] . son [1] = 3;
tree [3] . data='d' ;
tree [4] .data='c' ;
tree [4] . son [1] = 5;
tree [4] . son [2] = 6;
tree [4] . son [3] = 8;
tree [5] .data='e';
tree [6] .data='f' ;
tree [6] . son [1] = 7;
tree [7] . data='h';
tree [8].data='g';
//输出b、c、f、h结点的子结点(的值)
for (int i=1;i<maxn; i++) {
if(tree[i].data == 'b' | |tree[i] .data == 'c'||tree [i] .data == 'f' | |tree [i] .data == 'h') {
tree node n = tree[i];
bool flag = false;
for(int j=1;j<maxn;j++) {
if(n.son[j] != 0){//子节点存在
cout << tree[n.son[j]].data << "";//输出子结点的值
flag = true;
}
}
if(!flag)//不存在任何子节点的情况
cout << "LEAF";
cout << endl;
}
}
return 0;
}
本次分享的知识点
- 树的定义
- 树的基本概念(名词术语)
- 树的3种表示和存储方法
- 树的3种遍历方法
1、数据结构中的树结构最适合用来表示?C
A、有序数据元素
B、无序数据元素
C、元素之间具有分支层次关系的数据
D、元素之间无联系的数据
2、链表不具备的特点是?A
A、可随机访问任何一个元素
B、插入、删除操作不需要移动元素
C、无需事先估计存储空间大小
D、所需存储空间与存储元素个数成正比
访问子节点-2
指定下图所示的树结构,圆圈内是树结点的值。请你编程保存这个树的信息,然后输出b、c、f、h结点的子结点(的值)。输出4行,每个结点的子结点占一行。有多个子结点时,从左到右排列,之间以空格隔开;
没有子节点的输出"LEAF"。(要求用孩子兄弟表示法表示和保存树结构)
【输入样例】无
【输出样例】
d
e f g
h
LEAF

完整代码
cpp
#include<iostream>
using namespace std;
#define maxn 10
struct tree node{
int firstSon;//第一个孩子结点
int right;//右第一个兄弟结点
char data; //节点保存的数值
};
tree_node tree[maxn] ; //
int main () {
//保存树结构
tree [1] .data='a' ;
tree [1] . firstSon = 2;
tree [1] .right = 4;
tree [2] .data='b';
tree [2] . firstSon = 3;
tree [3] .data='d' ;
tree [4] . data='c';
tree [4] . firstSon = 5;
tree [5] .data='e';
tree [5] .right = 6;
tree [6] . data='f' ;
tree[6] .firstSon = 7;
tree[6] .right = 8;
tree [7] .data='h';
tree [8] .data='g';
//输出b、c、f、h结点的子结点(的值)
for(int i=1;i<maxn; i++) {
if (tree [i] .data == 'b' | |tree [i] .data == 'c'||tree [i] .data == 'f' | |tree[i] .data == 'h') {
int sonID = tree[i] .firstSon;
bool flag = false;
while (sonID != 0) {
flag = true;
tree_node n= tree[sonID];//第一个子节点
cout << n.data << " ";
sonID= n.right;// 下一个兄弟(编号)
};
if(!flag)//不存在任何子节点的情况
cout << "LEAF";
cout << endl;
}
}
return 0;
}