C++认识数

分享内容

  1. 树的定义
  2. 树的表示和存储
  3. 树的遍历

1.树的定义和概念

数据结构

数据结构可分为"线性"和"非线性"两大类:

树(数据结构)

自然界的一棵树有树根、有树杈、有叶子。

将这棵树上下翻转,就是数据结构中常见的树结构了。

树的定义

一棵树是由n(n≥0)个元素组成的有限集合。

n=0,表示空树

一棵树是由n(n≥0)个元素组成的有限集合。

① 组成树的每个元素称为结点(node),或节点。

② 连接两个结点的线段称为边。

③任意一棵非空树中(n>0),有一个特定的结点,称为根结点或树根(root)。

④根结点有且仅有一个。

⑤除了根结点外,其他每个结点都只有一个前驱结点;而每个结点的后继结点数是不确定的。

因此树结构是非线性结构。

树结构的基本概念

树结构中用到的专业名词较多,因此以下图的树结构为例,介绍相关概念。

  1. 父结点和子结点:
    父结点是某结点的前驱结点,而子结点则是某结点的后继结点。
    例如,3是6的父结点,7、8是6的子结点。
  2. 兄弟结点:父结点相同的结点。
    例如,4、5、6的父结点都是3,因此这三个结点互为兄弟结点。
  3. 祖先结点:
    一个结点的父结点和父结点的祖先。或者说,一个结点到根结点的路径上,除了它
    本身外的所有结点。
  4. 后代结点:一个结点的子结点和子结点的后代。
    例如,图片中的结点6的祖先结点有1、3。
    结点6的后代结点有7、8、9、10。
    5. 叶子结点:子结点数为0的结点。即最末端的结点。
    例如,图中的绿色的都是叶子结点。
  5. 子树:是指以某个结点为根的树的一部分。具体来说,对于树中的任意一个结点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;
}

本次分享的知识点

  1. 树的定义
  2. 树的基本概念(名词术语)
  3. 树的3种表示和存储方法
  4. 树的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;
}
相关推荐
2023自学中2 小时前
Qt 简易聊天室(第二篇),单线程 改为 多线程,方便后续增加文件传输功能
c++·qt
W_326002 小时前
Python文件进阶:一维数据与 CSV 文件读写
开发语言·python
Tyler_TXZ3 小时前
C++C语言之——二叉树
c语言·开发语言·数据结构·c++·二叉树
有点。3 小时前
C++二叉树二(练习题)
数据结构·c++·算法·图论
不会代码的小猴3 小时前
C++新增关键字
开发语言·c++·笔记
yaoxin5211233 小时前
497. Java 反射 - 使用反射读取注解
java·开发语言·python
2019一路前行3 小时前
Python 函数式编程
开发语言·python
liulilittle4 小时前
并发与内存安全术语:定义、分类与关联
c++·安全·并发·术语
_wyt0014 小时前
从图到树:9道洛谷基础题带你入门树形结构
c++·