主要内容
树的基本术语
1)结点:树中的一个独立单元,包括数据域(存储数据元素信息)和一个或若干个指针域(如存储直接后继位置的域)。
/*-------结点的数据结构-------*/
typedef int Elemtype /*设置数据(元素)的类型为整型*/
typedef struct Node
{
Elemtype data;
struct Node *next; /*一个或若干个指针*/
} Node;
2)结点的度:结点拥有的子树数目。
3)树的度:树中各结点最大的度。
4)根结点:没有父结点(又称双亲结点)的结点,树的起点。
5)内部结点:除根结点以外,度不为0的结点,又称非终端结点或分支结点。
6)叶结点:度为0的结点,又称终端结点。
7)子结点:相对于父结点而言的子树的根结点,又称孩子结点。
8)兄弟结点:同一父结点的子结点。(联系树的存储结构)
9)树的层次:根结点在第一层,它的子结点在第二层,往下类推。
10)堂兄弟结点:父结点在同一层的结点。(联系树的存储结构)
11)树的深度:树的最大层次数。
12)结点的前驱后继:结点的前驱后继信息只能在遍历树的动态过程中得到,与父结点和子结点的概念无关,区别于直接前驱后继。(联系线索二叉树)
13)满二叉树:每一层的结点数都为最大数值2的n-1次方;
14)完全二叉树:叶结点只存在于最后两层;每个结点的右分支下的结点所在最大层次,必定大于或等于其左分支下的结点最大层次,即只有最后一层的靠右结点可以为空。
二叉树(Binary Tree)
二叉树的每个结点至多只有两棵子树,且子树有左右之分,次序不能颠倒。
性质
1)二叉树的第n层至多有2的n-1次方个结点(画图看清);
2)深度为k的二叉树至多有2的k次方-1个结点(根据性质1,对等比数列求和);
3)对于任何一棵二叉树,如果其终端结点数为n0,度为2的结点数为n2,则 n0 = n2 + 1;
推导:设度为1的结点数为n1,则二叉树的总结点数 n = n0 + n1 + n2;除根结点外,每个结点只有一个父结点,即具有一个分支,所以 总结点数n 等于 根节点数1 + 总分支数m;又因为度为1的结点有一条分支,度为2的结点有两条分支,所以有 m = n1 + 2n2。
根据上述三条等式便可以得到 n0 = n2 + 1。
4)结点数为n的树的叶节点数:n/2向上取整,(n+1)/2向下取整。
存储结构
1)顺序存储结构:根据k层满二叉树的逻辑结构,设置一个数组长度为2的k次方-1的数组空间,每个结点对应一个位置,按照自上而下,从左到右的顺序存储。若对应位置上没有结点,则设置为NULL。
#define MAXSIZE 100
typedef Node SqBiTree[MAXSIZE]; /*设置数组*/
SqBiTree bt; /*定义二叉树bt*/
需要注意的是,这种顺序存储结构仅适用于完全二叉树。因为如果一般二叉树有许多空结点(NULL),这将浪费大量的存储空间,所以一般二叉树更适合采用链式存储结构。
2)链式存储结构:二叉树链表中的每个结点至少包含三个域:数据域和左、右指针域。有时为了便于找到结点的父结点,还可以增加一个指向父结点的指针域。利用这两种结点结构得到的二叉树存储结构分别称为二叉链表和三叉链表。链表的头指针指向二叉树的根结点。
我们还可以得到,含有n个结点的二叉链表中有n+1个空指针域,因此我们可以利用这些空指针域来存储其他有用的信息,从而得到另一种链式存储结构——线索二叉树。
/*稍微改变一下上面的“结点的数据结构”*/
typedef struct Binode
{
Elemtype data;
struct Binode *left, *right; /*指向左、右子树根结点的指针*/
} Binode, *BiTree; /*Binode表示二叉树结点内容,Bitree表示二叉树的起始存储位置*/
一般树的存储结构
1)双亲表示法:
这种存储结构利用了每个结点(除根结点外)只有唯一父结点的性质,每个结点包含数据域和父结点指针域,所有结点存储在一个数组空间中。
优点:便于查找某个结点的的父结点或祖先结点。
缺点:查找子结点时需要遍历整个存储结构。
typedef struct Node1
{
Elemtype data;
struct Node1 *parent; /*指向父结点*/
} BiTree[MAXSIZE];
2)孩子表示法:
每个结点包含数据域和指向子树根结点或兄弟结点的指针域。
一棵二叉树具有n个结点,存储结构中就有n条单链表。每条单链表以二叉树的结点X为首元结点,结点X的子树根结点为单链表上的其他结点。所有结点存储在一个线性表中。(类比图的邻接表存储结构)
这种存储结构的优缺点与双亲表示法恰好相反。
typedef struct Node2
{
Elemtype data;
struct Node2 *next; /*指向子树根结点或下一个兄弟结点*/
} BiTree[MAXSIZE];
/*--------结合双亲表示法和孩子表示法---------*/
typedef struct Node2 /*每条单链表上的其他结点*/
{
Elemtype data;
struct Node2 *nextsibling;
} Node2;
typedef struct Node1 /*每条单链表的首元结点结构*/
{
Elemtype data;
struct Node1 *parent; /*指向双亲结点*/
struct Node2 *firstchild; /*指向第一个子树根结点*/
} BiTree[MAXSIZE];
3)孩子兄弟法:
每个结点包含数据域和两个指针域,分别存储指向第一个子结点的指针和指向下一个兄弟结点的指针。
这种存储结构最大的优点就是它和二叉树的二叉链表表示完全一样,便于将一般的树结构转换为二叉树处理,利用二叉树的算法来实现对树的操作。
因此孩子兄弟法是应用较为普遍的一种树的存储结构。
typedef struct Node3
{
Elemtype data;
struct Node3 *firstchild, *nextsibling;
} Node3, *BiTree;
对比上面两种表示法,孩子兄弟法的存储结构更加清晰,各结点之间的联系也更加密切。
森林的存储结构
按照孩子兄弟法的思想,我们可以将森林中所有树的根结点看作兄弟结点,把森林中第一棵树的根结点作为森林的“根结点”,于是便能将森林转换为一棵二叉树。所以森林的存储结构与孩子兄弟法相同。
(又挖个坑,以后可能会补上森林和二叉树的转换算法_(:з」∠)_)