一、前情提要
我们总结了二叉树的相关概念和性质,既然知道了二叉树是啥,那该怎么去存储呢?还是那两种存储结构,顺序存储和链式存储。以及二叉树的几种遍历,前序遍历,中序遍历,后序遍历,层序遍历。本篇博客会讲解并给出相关代码。
二、二叉树的存储
(一)二叉树的顺序存储
我们前面已经提到了顺序存储对树这种一对多的关系结构实现起来是比较困难的。但是二叉树是一种很特殊的树,由于它的特殊性,使得用顺序存储结构也可以实现。
二叉树的顺序存储结构就是用一维数组存储二叉树中的结点,并且结点的存储位置,也就是数组的下标要能体现结点之间的逻辑关系,比如双亲与孩子的关系,左右兄弟的关系等。
如果我们按从左到右,从上到下的顺序遍历完全二叉树时,顺序是这样的:
这时候我们发现,假设双亲节结点下标为k,那么它的两个孩子结点下标分别是2*k和2*k+1。说明孩子结点和双亲结点的下标是相对应的,因此我们可以用顺序存储来进行描述。
比如下图:
根结点A的下标是1,它的两个孩子结点的下标就是2和3。以此类推。
那对于一般的二叉树呢?
如下图:
我们也可以利用完全二叉树的编号来实现,当对应的结点是空结点,直接修改值为NULL。
如果是一个最特殊的二叉树——斜树。我们开辟的空间数远超过实际使用的空间,这样空间就被浪费了。所以此时顺序存储结构可行,但是不合适。所以顺序存储一般只用于完全二叉树。
(二)二叉树的链式存储
既然顺序存储的通用性不强,我们就要考虑链式存储结构。
二叉树每个结点最多有两个孩子,所以为它设计一个数据域和两个指针域是一个比较自然的想法。我们称这样的链表叫做二叉链表。
结构图如下所示:
其中Data是数据域,Lchild和Rchild是指针域,分别存放指向左孩子和右孩子的指针。
我们可以用如下代码去定义结点结构:
typedef struct BitNode
{
int Data;
struct BitNode* Lchild, * Rchild;
}BitNode;
说完了改用何种结构去表示,那么该如何去遍历呢?
总共有三种遍历方式:前序遍历,中序遍历,后序遍历,层序遍历。
前序,中序,后序中的前中后,代表根结点访问的顺序。例如,前序遍历,就是最先访问跟根结点。
对于二叉树的遍历,书中有这样的定义:
二叉树的遍历是指从根结点出发,按照某种次序依次访问二叉树中所有的结点,使得每个结点被访问一次且仅被访问一次。
这个定义理解起来不难。
(1)前序遍历
规则:若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。
理解起来有点小抽象,举个例子画图慢慢看。
先处理以B为根结点的左子树进行前序遍历:
由于结点B没有右子树,所以继续遍历左子树。
再处理以D为根结点的左子树进行前序遍历:
此时根结点为A的左子树全部遍历完了,再来遍历右子树。
再处理以C为根结点的右子树进行前序遍历:
再处理以E为根结点的左子树进行前序遍历:
最后别忘了以F为根结点的子树(仅一个结点)进行前序遍历:
根据这一套流程,就可以将前序遍历的结果写出来:ABDGHCEIF。
(2)中序遍历
规则:若树为空,则空操作返回,否则从根结点开始(注意不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。
我们举相同的例子。
先处理以A为根结点的树:
再处理以B为根结点的树:
再处理以D围殴根结点的树:
此时我们就把整棵树左子树给遍历完了
此时的中序遍历结果:GDHBA
再处理以C为根结点的树:
再处理以E为根结点的树:
此时就将整棵树遍历完了。
完整的中序遍历顺序是:GDHBAEICF
(3)后序遍历
规则:若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。
还是以之前的例子去写。
先处理以B为根结点的树:
再处理以D为根结点的树:
至此A结点的左子树已遍历完成。
此时后序遍历的结果是:GHDB
再处理以C为根结点的子树:
再处理以E为根结点的子树:
现在已经遍历完了所有的结点。
后序遍历的结果:GHDBIEFCA
(4)层序遍历
规则:若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。
以上面的例子举例的话,层序遍历的结果是:ABCDEFGHI
PS:这篇文章只讲对应的概念,具体代码的写法会在下一篇文章中会分析并给出。