文章目录
05 树
5.1 树的定义和术语
(1)树是一类重要的 非线性 数据结构,是以分支关系定义的层次结构。
(2)几个概念:
-
结点的度:结点的子树数
-
树的度:一棵树中最大的结点度数
-
深度:树中结点的最大层次数
(3)二叉树
性质:
-
每个结点至多有两个子树,且结点有左右子树之分;
-
二叉树的第 i i i 层至多有 2 i − 1 2^{i-1} 2i−1 个结点;
-
深度为 k k k 的二叉树,总的结点个数至多为 2 k − 1 2^k-1 2k−1;
-
对任意一棵二叉树,若其叶子结点数为 n 0 n_0 n0,度为
2 的结点数为 n 2 n_2 n2 ,则 n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1。
(4)满二叉树
深度为 k k k,且结点总数为 2 k − 1 2^k-1 2k−1 个。也就是说只有度为 0 和 2 的结点,不存在度为 1 的结点。
(5)完全二叉树
性质:
-
度小于 2 的结点仅出现在最后两层,且结点要么有左右孩子,要么只有左孩子,不会出现仅有右孩子的情况;
-
具有 n n n 个结点的完全二叉树的深度为 log 2 n + 1 \log_2n +1 log2n+1。
5.2 树型结构的存储
5.2.1 树的存储结构
(1)双亲表示法,即储存结点之间的双亲关系,也就是各结点及其双亲。
结点结构
typedef struct PTNode
{
TElemType data; // 数据域
int parent; // 双亲域
}PTNode;
树结构
#define TREE_SIZE 100 // 树结点数目
typedef struct
{
PTNode nodes[TREE_SIZE];
int r, n; // 根结点的下标和结点总数
}PTtree;
初始化
PTtree T;
T.r = 0; // 根结点位置
T.n = 9;
这样找双亲容易,找孩子难。
(2)孩子表示法:① 用一个多重链表来表示,每个结点有多个指针域,分别指向其孩子结点;② 每个结点的孩子用单链表存储,再用长度为 n n n 的数组存放每个结点的数据域及其单链表的头指针。
定义孩子结点
typedef struct CTNode
{
int child; // 孩子结点的位置下标
struct CTNode *next;
}*ChildPtr;
定义数组结点,放置结点数据和孩子结点的指针
typedef struct
{
TElemType data; // 结点
ChildPtr firstchild;
}CTBox;
定义完整树结构
typedef struct
{
CTBox nodes[TREE_SIZE];
int r, n; // 根结点下标和结点总数
}
但是这种表示法找孩子容易,找双亲难。
(3)带双亲的孩子链表:结合双亲表示法和孩子表示法
(4)孩子-兄弟表示法:储存该结点第一个孩子的位置和兄弟结点的位置
定义结点结构
typedef struct CSnode
{
ElemType data; // 数据域
struc CSnode *firstchild, *nextsibling; // 指向第一个孩子和下一个兄弟结点
}CSnode, *CSTree;
5.2.2 二叉树的存储结构
(1)顺序存储结构,即按层次遍历,将元素依次放入数组中。
(2)二叉链表表示法,每个结点包括数据域及其左右孩子的指针
typedef struct BiTNode
{
TElemType data; // 数据域
struct BiTNode *leftchild, *rightchild;
}BiTNode, *BiTree;
这样找孩子容易,找父母难。
(3)三叉链表表示法,每个结点包括数据域及其左右孩子和双亲的指针,即一个数据域、三个指针域
typedef struct TriTNode
{
TElemtype data; // 数据域
strut TriTNode *leftchild, *rightchild, *parent;
}TriTNode, *TriTree;
这样找孩子和双亲都比较容易。
5.2.3 树与二叉树的相互转换
(1)将树转换为二叉树
① 加线:兄弟结点之间连线;
② 抹线:对每个结点,抹去除左孩子之外的其他孩子之间的连线;
③ 以树的根结点为轴心,将整树顺时针转 45°。
注意:树转换得到的二叉树右子树一定为空。
(2)将二叉树还原成树
① 加线:连祖孙,若 p 是双亲的左孩子,则将 p 的右孩子,右孩子的右孩子… 与 p 的双亲结点相连;
② 抹线:断父子,抹掉原二叉树中双亲与右孩子之间的连线;
③ 将结点按层次排列,形成树结构。
注意:只能将右子树为空的二叉树还原成树
5.2.4 森林和二叉树的相互转换
(1)将森林转换为二叉树
① 将各棵树分别转换成二叉树;
② 将每棵树的根结点用线相连;
③ 以第一棵树的根结点为二叉树的根,再以根结点为轴心,顺时针旋转,构成二叉树的结构。
注意:森林转换成的二叉树右子树非空。
(2)将二叉树还原成森林
① 抹线:将二叉树中根结点与其右孩子连线,及沿右分支找
到的所有右孩子间连线全部抹掉,使之变成孤立的二叉树;
② 还原:将孤立的二叉树分别还原成树。
注意:右子树不为空的二叉树只能还原成森林。
5.3 树型结构的遍历
5.3.1 二叉树的遍历
(1)先序遍历
void PreOrderTraverse(BiTree T)
{
if(T)
{
printf("%c\t", T->data);
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
}
}
(2)中序遍历
void InOrderTraverse(BiTree T)
{
if(T)
{
PreOrderTraverse(T->lchild);
printf("%c\t", T->data);
PreOrderTraverse(T->rchild);
}
}
(3)后序遍历
void PostOrderTraverse(BiTree T)
{
if(T)
{
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
printf("%c\t", T->data);
}
}
(4)层次遍历
5.3.2 树的遍历
(1)先根遍历:先访问树的根结点,然后依次先根遍历根的每棵子树
先根遍历树等价于先序遍历对应的二叉树
(2)后根遍历:先依次遍历每棵子树,然后访问根结点
后根遍历树等价于中序遍历对应的二叉树
(3)层次遍历
5.4 二叉树的应用
5.4.1 哈夫曼树
哈夫曼树:带权路径长度最短的树
注意:每次都在森林中选根结点值最小的树构造新的二叉树。