树与二叉树
树是一种非线性的递归结构,存储的数据元素关系为“ 一对多”
1、 定义
概念:
- 结点:祖先结点、子孙结点、双亲结点、孩子结点、兄弟结点、根结点
- 度:子结点个数
- 分支结点(非终端结点):度大于0的结点
叶子结点(终端结点):度为0的
根结点:没有父节点的 - 层次:从根开始的层数(根为1层)
深度:从根向下
高度:从叶向上 - 路径:两个结点之间所经过结点序列(路径自上而下,同双亲的两个孩子结点不存在路径)
注:空树没有结点
树的性质:
- 结点数=度数+1
- 度为m的数第i层至多有mi-1个结点
- 高度h的m叉树上至多有 m h − 1 m − 1 \frac{m^h-1}{m-1} m−1mh−1个结点
- n个结点的m叉树的最小高度为 l o g m ( n ( m − 1 ) + 1 ) log_m(n(m-1)+1) logm(n(m−1)+1)(向上取整数)
2、 二叉树
2.1 二叉树性质
- 非空二叉树上叶子结点数等于度为2的结点数加1,即n0=n2+1
结点总数n=n0+n1+n2=n1+2n2+1 - 非空二叉树上第k层有至多2k-1个结点(k>=1)
- 高度h的二叉树至多有2h-1个结点(h>=1)
- n个结点的完全二叉树的高度为log2(n+1)(向上取整数)
- n个结点有 C 2 n n n + 1 \frac{C_{2n}^n}{n+1} n+1C2nn种不同的二叉树(与栈相同)
2.2 几种特殊的二叉树
满二叉树
除了叶子结点,每个结点度都为2
完全二叉树
二叉排序树
平衡二叉树
2.3 存储结构
顺序结构
用一组连续地址按从上到下,从左到右的逻辑去存储二叉树上的结点元素
(适用于完全二叉树和满二叉树)
注:1. 需要从数组下标1开始存储树中的结点
2、树的顺序存储,数组下标代表结点编号
二叉树的顺序存储,数组下标既代表结点编号,也代表个结点之间关系
3、二叉树可以采用树的存储结构储存,但树并不一定能采用二叉树的存储结构
- 双亲表示法:用一组连续的存储空间存储树的结点,同时在每个结点中,用一个变量存储该结点的双亲结点在数组中的位置。
链式存储
二叉树的空间利用率低,适合链式
- 孩子表示法:把每个结点的孩子结点排列起来存储成一个单链表。所以n个结点就有n个链表; 如果是叶子结点,那这个结点的孩子单链表就是空的; 然后n个单链表的的头指针又存储在一个顺序表(数组)中。
- 孩子兄弟表示法:顾名思义就是要存储孩子和孩子结点的兄弟,具体来说,就是设置两个指针,分别指向该结 点的第一个孩子结点和这个孩子结点的右兄弟结点。
孩子兄弟表示法结点定义
typedef struct BiTNode{
int data; //数据域
struct BiTNode *lchild, *rchild; //左右孩子指针
}BiTNode,*BiTree;
2.4 线索二叉树
二叉树遍历
- 先序遍历(NLR)
void PreOrder(BiTree T){
if(T!=NULL){
visit(T); //访问根节点
PreOrder(T->lchild); //遍历左子树
PreOrder(T->rchild);
}
}
- 中序遍历(LNR)
void InOrder(BiTree T){
if(T!=NULL){
InOrder(T->lchild);
visit(T);
InOrder(T->rchild);
}
}
- 后序遍历(LRN)
void PostOrder(BiTree T){
if(T!=NULL){
PostOrder(T->lchild);
PostOrder(T->rchild);
visit(T);
}
}
- 层次遍历(利用队列)
void LevelOrder(BiTree T){
InitQueue(Q); //初始化辅助队列
BiTree p;
EnQueue(Q,T); //根结点入队
while(!IsEmpty(Q)){
DeQueue(Q,p); //队头元素出队
visit(p); //访问p所指结点
if(p->lchild!=NULL)
EnQueue(Q, p->lchild); //左子树入队列
if(p->rchild!=NULL)
EnQueue(Q, r->lchild); //右子树入队列
}
}
- 构建二叉树
先用先序或者后序,找到根节点,在中序里将左右子树分开。循环操作,求出树型。
若前序和后序相反,则:
- 二叉树高等于结点数
- 每个分支结点至多只有左孩子或者右孩子
- 只有一个叶子结点
遍历的作用:对于已知树求结点双亲、结点的孩子结点,求二叉树深度、二叉树的叶子结点数、判断两棵二叉树是否相同等
线索二叉树
- 基本概念
为了加快查找结点前驱和后继的速度,利用链表表示的二叉树中存在大量空指针(在n个结点的二叉树中存在n+1个空指针)。
二叉树是逻辑结构
线索二叉树是物理结构(是二叉树在计算机中的一种存储结构)
线索化规定:若无左子树,令lchild指向其前驱结点;若无右子树,令rchild指向其后继结点
结点结构如下图,标志域为表明指向结点还是直接前驱
结点存储结构
typedef struct ThreadNode{
int data;
struct ThreadNode *lchild, *rchild;
int ltag, rtg; //左右线索标志
}ThreadNode, *ThreadTree;
- 构造
线索化:对二叉树进行遍历使其变为线索二叉树的过程
- 遍历
注意:在进行线索树遍历时,先序和中序遍历可以直接进行,而后序遍历需要栈的支持
3、 树、森林
3.1 存储结构
双亲表示法
求孩子结点时需要遍历整个结构
孩子表示法
寻找双亲需要遍历n个结点中孩子链表指针域所指向的n个孩子链表
孩子兄弟表示法(二叉树表示法)
结点包含:(结点值、指向结点第一个孩子结点的指针、指向结点下一个兄弟结点的指针)
方便进行树转换为二叉树操作,易于查找结点的孩子,但不适合找双亲。
3.2 树、森林与二叉树转换
树->二叉树:左指孩子,右指兄弟。根结点无兄弟,故转换后二叉树无右子树。
二叉树->树
森林->二叉树:1.连接所有根 2.每棵树转为二叉树 3.以第一棵树的根顺时针旋转45°
二叉树->森林:断掉根结点向右的所有线,将断开的二叉树转为树。(二叉树转为树 或者森林是唯一的)
注意:树->二叉树后,无右孩子的结点数=分支结点+1(除叶结点外均为分支结点)
3.3 树和森林的遍历
树 | 森林 | 二叉树 |
---|---|---|
先根遍历 | 先序遍历 | 先序遍历 |
后根遍历 | 中序遍历 | 中序遍历 |
4、 应用
4.1 二叉排序树
特性:
- 左子树非空,左子树上所有结点关键字值都小于根结点的关键字值
- 右子树非空,右子树上所有结点关键字值都大于根结点的关键字值
对二叉排序树进行中序遍历,能够得到递增的有序序列
二叉排序树的插入规则为:关键字K小于根结点就左插,大于根结点右插。
删除结点:
①删除的是叶子结点
方法:直接删去该结点即可
②删除的是仅有左子树或者右子树的结点
方法:“子承父业”
③删除的是左右子树都有的结点
方法:令后继结点代替待删除结点,然后对该后继结点进行删除操作。(前驱结点同样可以)
4.2 平衡二叉树
定义:任意结点的左右子树高度差的绝对值不超过1的二叉排序树,简称平衡树(AVL)
平衡因子:结点左右子树高度差
插入:
- LL(右转)
- RR(左转)
- LR(先左后右)
- RL(先右后左)
注:多处不平衡时,优先转下部分树枝
含有n个结点平衡二叉树的最大深度为O(log2n),因此,平衡二叉树的平均查找长度为O(log2n)
4.3 哈夫曼树
定义
哈夫曼树(最优二叉树):WPL最小的二叉树
带权路径长度(WPL):所有叶结点的带权路径(从根结点到该结点间路径长度与该结点的权的乘积)长度之和
W
P
L
=
∑
i
−
1
n
w
i
l
i
WPL=\sum_{i-1}^nw_il_i
WPL=i−1∑nwili
如下图中所示带权路径长度为:WPL=3x7+3x5+1x2+2x4=46
注意:一棵有n个叶子结点的Huffman树有2n-1个结点
构建过程
- 从n个权值中取出两个最小的权值,然后小的放左边,构建树。根结点权值为两个权值相加为根结点权值,加入到原来的n-2个权值序列中
- 继续1操作,直至最终只剩下唯一根结点权值。当前树为哈夫曼树
哈夫曼编码
规定树中左子树标记0,右子树标记1。从结点开始,写出每个结点的路劲标记,即为编码。
-
上图编码为
- a:0
- b:10
- c:110
- d:111
考研tips:
本章内容多以选择题形式考察,算法题会涉及树遍历相关操作
树和二叉树的性质、遍历操作、转换(二叉树-树-森林)、存储结构和操作特性
满二叉树、完全二叉树、线索二叉树、哈夫曼树的定义和性质
二叉排序树和二叉平衡树的性质和操作
小狼的相关博文:
-
数据结构和算法系列代码测试代码