数据结构学习笔记——树的存储结构以及树、森林与二叉树之间的转换

简介: 数据结构学习笔记——树的存储结构以及树、森林与二叉树之间的转换

一、树的存储结构


树的存储结构中反映的是一棵树中各结点之间的关系,在存储中,不仅存储树中每个结点的值,还存储各结点之间的关系,主要有三种存储结构,分别是双亲表示法、孩子链表示法和孩子兄弟表示法。


(一)双亲表示法


双亲表示法是通过采用一维数组来存储树中的结点,其中每个结点被赋予一个结构体类型,包含data域和parent域,分别存储结点的数据域和存储该结点双亲的数组下标。

#define MAXSIZE 100
typedef struct
{
  int data;  //数据域
  int parent;  //双亲位置域
}ParNode[MAXSIZE];


例如,下图该树,通过双亲表示法进行存储,规定从数组下标为0开始存储,根结点下标为0,同时设根结点的parent域为-1:

1667293703606.jpg

1667293712993.jpg


通过双亲表示法中可以很容易地找到每个结点的双亲和祖先,但是其缺点是若寻找结点的孩子结点或兄弟结点就较为麻烦,需遍历整个数组。


(二)孩子链表表示法


将所有结点存放在一个顺序表中,每个数据元素有两个域,分别是该结点的数据域和存放该结点的第一个孩子的地址,同时为树中每个结点都构建一个单链表,它也有两个域,分别是存放该孩子结点在顺序表中的数组下标和存放下一个孩子的地址。

#define MAXSIZE 100
/*顺序表定义*/
typedef struct
{
  int data;    //数据域
  ChildNode *children;  //第一个孩子的地址
}Head;
/*单链表定义*/
typedef struct Node
{
  int address;  //该孩子结点在顺序表中的数组下标
  struct Node *next;  //下一个孩子的地址
}ChildNode;
Head T[MAXSIZE];  //建立顺序表头结构


例如,通过孩子链表表示法进行存储上树,规定顺序表下标为0的位置存放根结点:

1667293738240.jpg


通过孩子链表表示法可以很容易地找到该结点的孩子结点,但是若要找到结点的双亲则较为麻烦,需遍历n个结点中孩子链表指针域所指向的n个孩子链表。


(三)孩子兄弟表示法


孩子兄弟表示法是采用二叉链表,其中包含三个域,分别是数据域和两个指针,即*child指向第一个孩子结点的地址、*brother指向该结点的下一个兄弟结点的地址。

typedef struct Node
{
  int data;  //数据域
  struct Node *child,*brother;  //第一个孩子结点的地址和下一个兄弟结点的地址
}ChildBNode;


例如,通过孩子兄弟表示法进行存储上树:

1667293757948.jpg


这种方法的缺点是若从当前结点查找其双亲结点较为麻烦,解决方法是为每个结点增设一个parent域用于指向其父结点。


二、树、森林与二叉树的转换


(一)树转换成二叉树


例如,下图是一棵树,将其转换成二叉树:

1667293782438.jpg

1、首先为树中所有兄弟结点之间连线:

1667293791113.jpg

2、对树中每个结点只保留它与左边第一个孩子结点之间的连线,其余连线都删除:

1667293800469.jpg

3、以根结点为轴心,顺时针旋转45°,即可得到下图二叉树:

1667293809326.jpg

我们可以得出,一棵树转换为二叉树的途中,转换之后的右孩子结点是其原本的兄弟,例如结点C、D原本是兄弟,然后变成了B、C的右孩子,转换之后的二叉树的根结点无右子树。


(二)二叉树还原成树


二叉树还原成树的前提是该二叉树的根结点无右子树,将上图二叉树还原成树的步骤如下:

1、若某结点的左孩子有右子树,则在该结点与其左孩子的右子树的右分支上结点连线,例如结点A的左孩子B有右子树,则在结点A和其左孩子的右子树的右分支上连线:

1667293821611.jpg

2、删除各结点右子树的右分支与其父结点连线(图中橙线):

1667293830143.jpg

删除后:

1667293838859.jpg

3、旋转整理即可还原得到树:

1667293845630.jpg


(三)森林转换成二叉树


森林转换成二叉树的大体步骤是采用孩子兄弟表示法,森林中的第一棵树的根结点为二叉树的根结点,第一棵树的子树为二叉树的左子树,二叉树的右子树为森林中剩余的树。

例如,下图是森林,将其转换成二叉树:

1667293857044.jpg

1、将该森林中每棵树转换为二叉树,为各个树中所有兄弟结点之间连线,即此时结点的左指针指向该结点的孩子,右指针指向该结点的兄弟结点:

1667293865053.jpg

2、将每棵树的根结点都视为兄弟结点的关系,将其连线:

1667293872400.jpg

3、然后对树中每个结点只保留它与左边第一个孩子结点之间的连线,其余连线都删除其余连线都删除:

1667293879752.jpg

4、以第一棵树的根结点为轴心,顺时针旋转45°,整理后,即可得到下图二叉树:

1667293886693.jpg


(四)二叉树还原成森林


将上图二叉树还原成森林的步骤如下:

1、若某结点是其双亲的左孩子,则将该结点的右孩子、右孩子的右孩子等等都与该结点的双亲结点连接:

1667293896788.jpg

2、删除二叉树中所有双亲结点与其右孩子结点的连线:

1667293904454.jpg

3、整理所得到的树和森林:

1667293912890.jpg


✨一个高度为h(h>0)的满二叉树对应的森林中所含的树的棵数为h。

注:不能说成是一颗完全二叉树,满二叉树是完全二叉树的特例,因为若一棵树是满二叉树,则它必是完全二叉树,但不能说一个完全二叉树必是满二叉树。


例如,下图是一棵高度为3的满二叉树,将其转换为森林:

1667293921552.jpg

1667293929785.jpg

1667293937232.jpg

由于该满二叉树的高度为3,即将其转换为森林后,该森林所含有的树也为3:

1667293950131.jpg


这个性质如果对于完全二叉树,则不成立。


例如,下图是一棵高度为3的完全二叉树,将其转换为森林:

1667293959257.jpg

1667293967195.jpg

1667293974128.jpg


可发现,经整理后,只得到两棵树,与高度无关:

1667293984792.jpg


三、树和森林的遍历


(一)树的遍历


树的遍历主要分为两种:先序遍历和后序遍历,和二叉树的遍历规则一样,例如下图该树:

1667293994981.jpg

该树的先序遍历序列为ABDEHMI,后序遍历序列为DBHMEIA。

另外也有层次遍历,该树的层次遍历序列为ABEIDHM。

将上图中的树转为二叉树后,如下:

1667294010029.jpg

我们知道该二叉树的先序遍历序列为ABDEHMI,中序遍历序列为DBHMEIA,后序遍历序列为DMHIEBA,可以得出:


✨树的先序遍历序列为转换成二叉树后的先序遍历序列,树的后序遍历序列为转换成二叉树后的中序遍历序列。


(二)森林的遍历


森林的遍历也是主要分为两种:

先序遍历和后序遍历,和二叉树的遍历规则一样,例如下图该森林:

1667294028755.jpg

对其进行先序遍历和后序遍历得到的序列分别为ABDEHMICFJKGL、DBHMEIAJFKCLG。

将上图中的森林转为二叉树后,如下:

1667294036951.jpg

对该二叉树进行先序遍历、中序遍历和后序遍历得到的遍历序列为:ABDEHMICFJKGL、DBHMEIAJFKCLG、DMHIEBJKFLGCA。

不难看出,可以得到以下结论:


✨森林的先序遍历序列为转换成二叉树后的先序遍历序列,森林的后序遍历序列为转换成二叉树后的中序遍历序列。

总结:

对应关系 森林 二叉树
- 先序遍历 先序遍历 先序遍历
- 后序遍历 中序遍历 中序遍历


只需记住:

1、树、森林、二叉树的先序遍历序列都是相同的。

2、森林和二叉树的先序遍历和中序遍历序列是相同的。


目录
打赏
0
0
0
3
22
分享
相关文章
|
26天前
|
算法系列之数据结构-Huffman树
Huffman树(哈夫曼树)又称最优二叉树,是一种带权路径长度最短的二叉树,常用于信息传输、数据压缩等方面。它的构造基于字符出现的频率,通过将频率较低的字符组合在一起,最终形成一棵树。在Huffman树中,每个叶节点代表一个字符,而每个字符的编码则是从根节点到叶节点的路径所对应的二进制序列。
41 3
 算法系列之数据结构-Huffman树
【数据结构进阶】AVL树深度剖析 + 实现(附源码)
在深入探讨了AVL树的原理和实现后,我们不难发现,这种数据结构不仅优雅地解决了传统二叉搜索树可能面临的性能退化问题,还通过其独特的平衡机制,确保了在任何情况下都能提供稳定且高效的查找、插入和删除操作。
76 19
|
3月前
|
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
【数据结构——树】哈夫曼树(头歌实践教学平台习题)【合集】目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:任务描述 本关任务:编写一个程序构建哈夫曼树和生成哈夫曼编码。 相关知识 为了完成本关任务,你需要掌握: 1.如何构建哈夫曼树, 2.如何生成哈夫曼编码。 测试说明 平台会对你编写的代码进行测试: 测试输入: 1192677541518462450242195190181174157138124123 (用户分别输入所列单词的频度) 预
92 14
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
|
3月前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
72 10
|
5月前
|
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
487 9
|
5月前
|
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
73 1
|
3月前
|
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
178 77
|
2月前
|
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
32 11
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
3月前
|
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
84 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等