《软件技术基础》之《树与二叉树》
树
树形结构指的是数据元素之间存在着“一对多” 的树形关系的数据结构,是一类重要的非线性的数据结构。
在树形结构中,树根结点没有前驱结点,其余每个结点有且只有一个前驱结点。叶子结点没有后继结点,其余每个结点的后继结点数可以是一个或多个。
树的定义
由一个或多个(n≥0)结点组成的有限集合T,有且仅有一个结点称为根(root),当n>1时,其余的结点分为m(m≥0)个互不相交的有限集合T1,T2,…,Tm。每个集合本身又是棵树,被称为这个根的子树。
n=0时为空树,空树也是树。
树的定义具有递归性,即树中还有树。
树的相关术语
- 结点(node)
包含数据项及指向其他结点的分支。 - 结点的度(degree)
指结点所拥有的子树棵数。 - 叶结点(leaf)
指度为零的结点,又称为终端结点。 - 分支结点(branch)
除叶结点外的其他结点,又称为非终端结点。 - 子女结点(child)
若结点x有子树,则子树的根结点即为结点x的子女。 - 父结点(parent)
若结点x有子女,则该结点即为子女的父结点。 - 兄弟结点(sibling)
同一父结点的子女互称为兄弟。 - 祖先结点(ancestor)
从根结点到该结点所经分支上的所有结点。 - 子孙结点(descendant)
指某一结点的子女,以及这些子女的子女都是该结点的子孙。 - 结点所处层次(level)
简称结点的层次,即从根结点到该结点所经路径上的分支条数。 - 树的深度(depth)
树中距离根结点最远的结点所处层次即为该树的深度,空树的深度为0,只有一个根结点的树的深度为1。(计算方向:从下往上) - 树的高度(height)
树的高度与树的深度计算的方向不同,但是数值相等。(计算方向:从上往下) - 树的度(degree)
树中结点的度的最大值。 - 有序树(ordered tree)
树中结点的各棵子树T0,T1,…是有次序的,即为有序树,其中,T1叫作根的第一棵子树,T2叫作根的第二棵子树,…。 - 无序树(unordered tree)
树中结点的各棵子树之间的次序是不重要的,可以互相交换位置。 - 森林(forest)
指m(m>0)棵树的集合。
树型结构与线性结构的对别
数据元素 | 树型结构 | 线性结构 |
---|---|---|
第一个数据元素 | 无前驱 | 无前驱 |
最后一个数据元素 | 无后继 | 无后继 |
其他数据元素 | 一个前驱、多个后继 | 一个前驱、一个后继 |
二叉树
一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
二叉树的特点
1)每个结点最多有两颗子树,所以二叉树中不存在度大于2的结点。
2)左子树和右子树是有顺序的,次序不能任意颠倒。
3)即使树中某结点只有一棵子树,也要区分它是左子树还是右子树。
二叉树的性质
3种特殊的二叉树
-
斜树:所有的结点都只有左子树的二叉树叫左斜树。所有结点都是只有右子树的二叉树叫右斜树。这两者统称为斜树。
-
满二叉树(完美二叉树):一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
-
完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
二叉树的存储方式
二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。
-
顺序存储:
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
-
链式存储:
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。
链式结构又分为二叉链和三叉链(红黑树)。
二叉树遍历
二叉树的遍历是指从二叉树的根结点出发,按照某种次序依次访问二叉树中的所有结点,使得每个结点被访问一次,且仅被访问一次。
二叉树的访问次序可以分为四种:
- 前序遍历
- 中序遍历
- 后序遍历
- 层序遍历
前序遍历
前序遍历通俗的说就是从二叉树的根结点出发,当第一次到达结点时就输出结点数据,按照先向左在向右的方向访问。
示例:
从根结点出发,则第一次到达结点A,故输出A;
继续向左访问,第一次访问结点B,故输出B;
按照同样规则,输出D,输出H;
当到达叶子结点H,返回到D,此时已经是第二次到达D,故不在输出D,进而向D右子树访问,D右子树不为空,则访问至I,第一次到达I,则输出I;
I为叶子结点,则返回到D,D左右子树已经访问完毕,则返回到B,进而到B右子树,第一次到达E,故输出E;
向E左子树,故输出J;
按照同样的访问规则,继续输出C、F、G;
则上示二叉树的前序遍历输出为:ABDHIEJCFG
中序遍历
中序遍历就是从二叉树的根结点出发,当第二次到达结点时就输出结点数据,按照先向左在向右的方向访问。
示例:
从根结点出发,则第一次到达结点A,不输出A,继续向左访问,第一次访问结点B,不输出B;继续到达D,H;
到达H,H左子树为空,则返回到H,此时第二次访问H,故输出H;
H右子树为空,则返回至D,此时第二次到达D,故输出D;
由D返回至B,第二次到达B,故输出B;
按照同样规则继续访问,输出J、E、A、F、C、G;
则上示二叉树的中序遍历输出为:HDIBJEAFCG
后序遍历
后序遍历就是从二叉树的根结点出发,当第三次到达结点时就输出结点数据,按照先向左在向右的方向访问。
示例:
从根结点出发,则第一次到达结点A,不输出A,继续向左访问,第一次访问结点B,不输出B;继续到达D,H;
到达H,H左子树为空,则返回到H,此时第二次访问H,不输出H;
H右子树为空,则返回至H,此时第三次到达H,故输出H;
由H返回至D,第二次到达D,不输出D;
继续访问至I,I左右子树均为空,故第三次访问I时,输出I;
返回至D,此时第三次到达D,故输出D;
按照同样规则继续访问,输出J、E、B、F、G、C,A;
则上示二叉树的后序遍历输出为:HIDJEBFGCA
前序遍历、中序遍历、后序遍历的代码实现
虽然二叉树的遍历过程看似繁琐,但是由于二叉树是一种递归定义的结构,故采用递归方式遍历二叉树的代码十分简单。
递归实现代码如下:
/*二叉树的前序遍历递归算法*/
void PreOrderTraverse(BiTree T)
{
if(T==NULL)
return;
printf("%c", T->data); /*显示结点数据,可以更改为其他对结点操作*/
PreOrderTraverse(T->lchild); /*再先序遍历左子树*/
PreOrderTraverse(T->rchild); /*最后先序遍历右子树*/
}
/*二叉树的中序遍历递归算法*/
void InOrderTraverse(BiTree T)
{
if(T==NULL)
return;
InOrderTraverse(T->lchild); /*中序遍历左子树*/
printf("%c", T->data); /*显示结点数据,可以更改为其他对结点操作*/
InOrderTraverse(T->rchild); /*最后中序遍历右子树*/
}
/*二叉树的后序遍历递归算法*/
void PostOrderTraverse(BiTree T)
{
if(T==NULL)
return;
PostOrderTraverse(T->lchild); /*先后序遍历左子树*/
PostOrderTraverse(T->rchild); /*再后续遍历右子树*/
printf("%c", T->data); /*显示结点数据,可以更改为其他对结点操作*/
}
层次遍历
层次遍历就是按照树的层次自上而下的遍历二叉树。
示例:
层次遍历结果为:ABCDEFGHIJ
代码:
层次遍历的详细方法可以参考二叉树的按层遍历法。
树、森林和二叉树之间的转换
树转换二叉树
- 加线:在所有兄弟结点之间加一条连线。
- 去线:树中的每个结点,只保留它与第一个孩子结点的连线,删除它与其它孩子结点之间的连线。
- 层次调整:以树的根节点为轴心,将整棵树顺时针旋转一定角度,使之结构层次分明。(注意第一个孩子是结点的左孩子,兄弟转换过来的孩子是结点的右孩子)。
二叉树转换树
是树转换为二叉树的逆过程。还原结点A的孩子,结点A的左孩子开始,一直向右走,这些结点就是结点A的孩子,遇见顺序就是它们作为结点A孩子的顺序。
-
加线:若某结点X的左孩子结点存在,则将这个左孩子的右孩子结点、右孩子的右孩子结点、右孩子的右孩子的右孩子结点…,都作为结点X的孩子。将结点X与这些右孩子结点用线连接起来。
-
去线:删除原二叉树中所有结点与其右孩子结点的连线。
-
层次调整。
森林转换二叉树
- 把每棵树转换为二叉树。
- 第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树的根结点的右子树,用线连接起来。
二叉树转换森林
假如一棵二叉树的根结点有右孩子,则这棵二叉树能够转换为森林,否则将转换为一棵树。在二叉树种A有右子树上向右的一连串结点都是A的兄弟,那么就把兄弟分离,A的每个兄弟结点作为森林中树的根结点。
- 从根结点开始,若右孩子存在,则把与右孩子结点的连线删除。再查看分离后的二叉树,若其根结点的右孩子存在,则连线删除…。直到所有这些根结点与右孩子的连线都删除为止。
- 将每棵分离后的二叉树转换为树。
给定先序和中序遍历恢复二叉树
见于《软件技术基础》之《数据结构习题解析》项目2:二叉树遍历。