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

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

一、树的存储结构


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


(一)双亲表示法


双亲表示法是通过采用一维数组来存储树中的结点,其中每个结点被赋予一个结构体类型,包含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、森林和二叉树的先序遍历和中序遍历序列是相同的。


相关文章
|
9月前
|
存储 算法 Java
算法系列之数据结构-二叉树
树是一种重要的非线性数据结构,广泛应用于各种算法和应用中。本文介绍了树的基本概念、常见类型(如二叉树、满二叉树、完全二叉树、平衡二叉树、B树等)及其在Java中的实现。通过递归方法实现了二叉树的前序、中序、后序和层次遍历,并展示了具体的代码示例和运行结果。掌握树结构有助于提高编程能力,优化算法设计。
305 10
 算法系列之数据结构-二叉树
|
11月前
|
Java C++
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
353 12
|
11月前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
196 10
|
11月前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
526 3
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
1048 9
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
304 59
|
6月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
136 0
栈区的非法访问导致的死循环(x64)
232.用栈实现队列,225. 用队列实现栈
在232题中,通过两个栈(`stIn`和`stOut`)模拟队列的先入先出(FIFO)行为。`push`操作将元素压入`stIn`,`pop`和`peek`操作则通过将`stIn`的元素转移到`stOut`来实现队列的顺序访问。 225题则是利用单个队列(`que`)模拟栈的后入先出(LIFO)特性。通过多次调整队列头部元素的位置,确保弹出顺序符合栈的要求。`top`操作直接返回队列尾部元素,`empty`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。
|
11月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
550 77
|
10月前
|
算法 调度 C++
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
250 11

热门文章

最新文章