目录
树的基本概念
形如上面给出的结构,就称为树。
每个圆圈表示一个结点,两两结点间的连线(边),表示结点间的关系。
结点
任何一棵树都有根结点,根结点是一棵树的最顶层结点。比如,上面给出的树的根结点为A。
结点间的关系
根结点:位于树的最顶层的结点。
父结点:与结点直接相连的上层结点。比如,B是E的父结点,A是B的父结点,同时A又是C、D的父结点。
子结点:如果A是B的父结点,则B是A的子结点。
兄弟结点:如果两个或多个结点的父结点相同,则称这些结点间为兄弟结点。
叶子结点:没有子结点的结点。
分支结点 :含有子结点的结点。
结点的性质
结点的度 : 结点的子结点的数目。
结点的层次:从根结点开始定义,根结点的深度认为为1,根结点的子结点为2,依次类推。
结点的深度:从根结点开始自顶向下逐渐累加。
结点的高度:结点到根结点的距离加一。
树的性质
子树:把子结点看作根结点而形成的树。
有序树和无序树:树的结点的子结点从左到右是有次序的,不能互换,称该树为有序树。否则称为无序树。
路径和路径长度:树的两结点结的路径是由这两个结点间所经过的结点序列构成的,而路径长度是路径上经过边的个数。
树的度 :树中结点的最大度数称为树的度。
**1)树的结点数目等于所有结点的度数之和加1。**度的个数等于边的个数,而每个边唯指向一个结点,但没有边指向根结点,所以结点数目还要在度的数目上加一。
二叉树的概念
二叉树的定义
二叉树是一种特殊的树,其特点是:
-
树中的每个结点至多有两个子树。
-
二叉树是一种有序树,子树有左右之分。
-
子结点分为左子结点和右子结点。
二叉树的性质
- 非空二叉树的叶子结点数等于度为2的结点数加1,即n0=n2+1。
含 n 个结点的二叉树 n 0 个叶子结点, n 1 个度为 1 的结点, n 2 个度为 2 的结点。 则有 n = n 0 + n 1 + n 2 ① 由 边的数目 + 1 = 结点数目 得 n 1 + 2 n 2 + 1 = n ② 联立①②求得, n 0 = n 2 + 1 含n个结点的二叉树 \\ n_0个叶子结点,n_1个度为\ 1\ 的结点,n_2个度为\ 2\ 的结点。\\ 则有 n = n_0 + n_1 + n_2 ①\\ 由\ 边的数目+1=结点数目\ 得 \\ n_1 + 2n_2 + 1 = n ② \\ 联立①② 求得,n_0=n_2+1 含n个结点的二叉树n0个叶子结点,n1个度为 1 的结点,n2个度为 2 的结点。则有n=n0+n1+n2①由 边的数目+1=结点数目 得n1+2n2+1=n②联立①②求得,n0=n2+1
- 含n个结点的二叉树,有n+1可空指针。
空指针总数 = 2 ∗ 叶结点数目 + 度为 1 的数目 = 2 n 0 + n 1 又 n 0 = n 2 + 1 则 2 n 0 + n 1 = n 0 + n 2 + n 1 + 1 = n + 1 空指针总数\ = \ 2 \ *\ 叶结点数目 + 度为\ 1\ 的数目 = 2n_0+n_1 \\ 又n_0=n_2+1 \\ 则\ 2n_0+n_1=n_0+n_2+n_1+1=n+1 空指针总数 = 2 ∗ 叶结点数目+度为 1 的数目=2n0+n1又n0=n2+1则 2n0+n1=n0+n2+n1+1=n+1
几个特殊的二叉树
满二叉树
最后一层结点全部为为叶结点,其余结点的度数均为2.
如果一棵满二叉树的高为h,则此满二叉树有2^h-1
个结点。
结点从根结点(根结点编号为1)其,自上而下、自左而右按层次编号,这样每个节点都会对应一个编号。对于编号为i的结点,若有双亲,则双亲的编号为[i/2]。若有左孩子,则左孩子编号为2i。若有右孩子,则右孩子编号为2i+1。
完全二叉树
高度为h、有n个结点的二叉树,当且仅当其每个结点斗鱼高度为h的满二叉树中编号为1~n的结点一一对应时,称为完全二叉树。
(1) 若i<=[n/2]
,则结点i为分支结点,否则为叶结点。
(2) 叶结点只可能出现在层次最大的两层上。对于最大层次中的叶结点,都依次排列在该层的最左边的位置。
(3) 度为1的结点只可能有一个,且该结点只有左孩子而无右孩子。
(4)按层序编号时,一旦出现某结点(编号为i)为叶结点或只有左孩子,则编号大于i的结点均为叶结点。
(5)若n为奇数,则每个分支结点都有左孩子和右孩子;若n为偶数,则编号最大的分支结点(编号为n/2)只有左孩子没有右孩子,其余分支结点左右孩子都有。
二叉排序树
左子树上所有结点的关键字小于根结点的关键字;右子树上的所有结点的关键字大于根节点的关键字;左子树和右子树又各是一棵二叉排序树。
平衡二叉树
树上任意一个结点的左子树与右子树的深度差不超过1。
二叉树的存储结构
顺序存储结构
一组地址连续的存储单元依次自上而下、自左而右存储二叉树。
顺序存储:
标黄表示结点不存,存放一个不存在的值占位。保证编号为i的结点的父节点为[i/2]
,左右子结点的编号为2i
和2i+1
。(从数组的索引为1的位置开始存储)
顺序存储常用于存放满二叉树和完全二叉树。
顺序存储一般树,会存在很大的空间浪费。
链式存储结构
由于顺序存储的空间利用率较低,因此二叉树一般采用链式存储结构(二叉链),用链表结点来存储二叉树的每个结点。
二叉链的每个结点至少包含三个域:数据域data、左指针域lchild、右指针域rchild。
存储结构描述如下:
typedef struct BiTNode
{
DataType data; // 数据域
struct BitNode *lchild, *rchild; // 指针域 左、右孩子指针
}BitNode, *BiTree;
同样的,用二叉链存储树,叶子结点或度为1的结点的指针域并没有完全使用上,存在很多指向空的指针域。所以,后面会介绍另一种树–线索二叉树,把结点的每个指针域全部使用上。
二叉树的遍历
二叉树的遍历 是指按某条搜索路径访问树中每个结点,使得每个结点均被访问一次,而且仅被访问一次。
遍历一棵二叉树,首先要决定对根结点N、左子树L和右子树R的访问顺序常见的访问次序有先序(NLR)、中序(LNR)和后序(LRN)三种遍历算法。其中“序”表示根结点何时被访问。
先序遍历
先序遍历的操作过程如下:
若二叉树为空,则什么也不做。否则,
1)访问根结点
2)先序遍历左子树
3)先序遍历右子树
对应的递归算法如下:
void PreOrder(BiTree T)
{
if (T == NULL) return;
visit(T); // 访问根结点
preOrder(T->lchild); // 递归遍历左子树
preOrder(T->rchild); // 递归遍历右子树
}
以下给出一个示例:
先序遍历所得的结点序列为 1 2 4 6 3 5。
中序遍历
中序遍历(InOrder)的操作过程如下:
若二叉树为空,则什么也不做。否则,
1)中序遍历左子树
2)访问根结点
3)中序遍历左子树
对应的递归算法如下:
void InOrder(BiTree T)
{
if (T == NULL) return;
InOrder(T->lchild); // 递归遍历左子树
visit(T); // 访问根结点
InOrder(T->rchild); // 递归遍历右子树
}
后序遍历
后序遍历(PostOrder)的操作过程如下:
若二叉树为空,则什么也不做。否则,
1)后序遍历左子树
2)后序遍历右子树
3)访问根结点
对应的递归算法如下:
void PostOrder(BiTree T)
{
if (T == NULL) return;
PostOrder(T->lchild); // 递归遍历左子树
PostOrder(T->rchild); // 递归遍历右子树
visit(T); // 访问根结点
}
层次遍历
从根结点开始,自上而下、自左而右遍历输出每个结点。
层次输出次序为: 1 2 3 4 5 6 7
实现层次遍历,借助一个队列即可。
队列,先进先出。
算法步骤:
(1)将根结点入队
(2)如果队列不为空,则从中取出结点,访问出队结点。否则,结束遍历。
(3)将从出队结点的左右子结点依次入队。
(4)重复第(2)步。
总结
三种遍历算法,时间复杂度都是O(n),空间复杂度都是O(n)。
三种遍历的相似度很高,递遍历、中序归进行的顺序是一致的,只是输出根结点的时机不同。
上面我们给出的先序、中序和后序遍历都是借助递归来实现的。当然,也可以用非递归来实现,借助栈可以实现。
由遍历序列构造二叉树
由二叉树的先序序列和中序序列可以唯一地确定一棵二叉树。
由二叉树的后序序列和中序序列也可以唯一确定一个二叉树。
但是,有二叉树的先序序列和后序序列不能唯一确定一棵二叉树。
线索二叉树
遍历二叉树是以一定的规则将二叉树的结点排成一个线性序列,从而得到几种遍历序列,使得该序列中的每个结点都有一个直接前驱和直接后驱。第一个结点没有直接前驱,最后一个结点没有直接后驱。
传统的二叉链表仅能体现一个父子关系,不能直接得到结点在遍历中的前驱或后驱。在含n个结点的二叉树中,有2n个指针,其中n+1为空指针。
由此尝试利用这些空指针来存放指向其前驱或后驱的指针。这样就可以像遍历单链表那样方便地遍历二叉树。引入线索二叉树正式为了加快查找结点的前驱和后驱的速度。
规定:若结点无左子树,令lchild指向其前驱结点;若无右子树,令rchild指向其后继结点。增加两个标志域标识指针域,用来指向左(右)或前继(后继)。
其中,标志域含义如下:
线索二叉树的存储结构描述如下:
typedef struct ThreadNode
{
DataType data; // 数据元素
struct ThreadNode *lchild, *rchild; // 左、右孩子指针
int ltag, rtag; // 左、右线索标志
}ThreadNode, *ThreadTree;
以这种结点结构构成的二叉链表作为二叉树的存储结构,成为线索链表
,其中指向结点前驱或后继的指针称为线索。加上线索的二叉树称为线索二叉树。
树、森林
树的存储结构
双亲表示法
这种存储结构采用一组连续空间来存储每个结点,同时在每个结点中增设一个伪指针
,指示其双亲结点在数组中的位置。根结点下标为0,其伪指针域为-1。
双亲表示法的存储结构描述如下;
#define MAX_SPACE_SIZE 100 // 树中最大结点数目
typedef struct // 树的结点定义
{
ElemType data; // 数据元素
int parent; // 双亲位置域
}PTNode;
typedef struct // 树的类型表示
{
PTNode nodes[MAX_SPACE_SIZE]; // 双亲表示
int n; // 结点数
}PTree;
该存储结构利用了每个结点(根结点除外)只有唯一双亲的性质,可以很快地得到每个结点的双亲结点,但求结点的孩子时则需遍历整个树结构。
注意:树的顺序存储结构中,数组下标代表结点编号,下标对应的内容指示结点间的关系。二叉树的顺序存储结构中,数组下标即代表结点的编号,又指示了结点间的关系。
二叉树属于树,所以二叉树可以用树的存储结构来存储,但反过来不行。
孩子表示法
孩子表示法是将每个结点的孩子结点都用单链表链接起来形成一个线性结构,此时n个结点就有n个孩子链表。
孩子兄弟表示法
以二叉链表作为存储结构。孩子兄弟表示法使结点包含三部分内容:结点值、指向结点第一个孩子结点的指针和指向结点下一个兄弟节点的指针。
孩子兄弟表示法的存储结构描述:
typedef struct CSNode
{
ElemType data;
struct CSCode *firstchild, *nextbrother;
}CSNode, *CSTree;
树、森林与二叉树的转换
二叉树和树都能用二叉链表作为存储结构,这为转换提供了可能。从物理结构上看,树与二叉树党的二叉链表结构是相同的,只是解释不同而已。
树转换成二叉树的规则:
(1)结点的左指针指向第一个孩子,右指针指向树中相邻右兄弟。
(2)根结点没有兄弟,所以对应的二叉树没有右子树。
对于一棵树,可以找到一个唯一的二叉树预支对应。
简单进行格式处理后,展示:
森林转换成二叉树:
转换规则:
(1)把森林的每棵树转换成二叉树。
(2)每棵树的根也可视为兄弟关系,每棵树的根之间加一根连线。