树
一、基本概念:根节点、分支节点、边、叶子节点
非空树的特性:a、有且仅有一个根节点
b、没有后继的结点称为“叶子结点”(终端结点)
c、有后继的结点称为“分支结点”(非终端结点)
除了根节点以外,任何一个结点有且仅有一个前驱
空树:结点树为0的树
树是n个结点的有限集合,n=0时,称为空树,这是一种特殊情况。
树是一种递归定义的数据结构
在任意一颗非空树中应该满足:
1)有且仅有一个特定的称为根的结点。
2)当n>1时,其余结点可分为m个互不相交的有限集合T1,T2,....Tm,其中每个集合本身又是一颗树,并且称为根节点的子树。
应用:文件系统
二、基本术语
a)结点之间关系的描述
祖先、子孙、双亲(父节点)、孩子结点、兄弟结点、堂兄弟结点、
路径、路径长度
b)结点、树的属性描述
结点的层次(深度)从上往下数
结点的高度--从下往上数
树的高度(深度)-----总共多少层
结点的度----有几个孩子(分支)(******)
树的度----各结点的度的最大值(******)
c)有序树、无序树
有序树---逻辑上看,树中结点的各子树从左至右是有次序的,不能互换
无序树---逻辑上看,树中结点的各子树从左至右是无次序的,可以互换
d)森林
森林----m颗互不相交的树的集合。
森林和树的转换(************)
三、树的常考性质
a)结点数 = 总度数 + 1
b) 度为m的树、m叉树的区别
度为m的树
树的度----各结点的度的最大值
任意结点的度<=m(z最多m个孩子)
至少有一个结点度为m(有m个孩子)
一定为非空树,至少有m+1个结点
m叉树
m叉树-----每个结点最多只能有m各孩子的树
任意结点的度<=m(z最多m个孩子)
允许所有的结点度都<m
可以为空树
c)度为m的树第i层至多有m^(i-1)个结点(i≥1)
m叉树的第i层之多有m^(i-1)个结点(i≥1)
d)高度为h的m叉树至多有(m^h -1)/(m-1)个结点
等比数列求和公式:a+aq+...+aq^n-1 = a(1-q^n)/(1-q)
高度为h,度为m的树至少有h+m-1个结点
e) 高度为h的m叉树至少有h个结点
高度为h,度为m的树至少有h+m-1个结点。
f) 具有n个结点的m叉树的最小高度为logm(n(m-1)+1)
高度最小的情况下,所有的结点都有m个孩子
二叉树
二叉树是n个结点的有限集合:
①或者为空二叉树
②或者由一个根结点和两个互不相交的被称为根的左子树和右子树组成。
左子树和右子 树又分别是一颗二叉树。
特点:①每个结点至多只有两棵子树②左右子树不能颠倒(二叉树是有序树)
满二叉树
特点
①不存在度为1的结点
②总结点数为2^h-1
③编号从1开始,结点i的左孩子为2i,右孩子为2i+1, 父结点为i/2(向下取整)
④只有最后一层有叶子结点
完全二叉树
特点
①只有最后两层存在叶子结点
②最多只存在一个度为1的结点
③编号的问题同满二叉树
④i>n/2(向下取整),为叶子结点,i<n/2(向下取整),为分支结点
二叉排序树
特点:左<根<右(关键字大小)
用途:元素的排序、搜索
平衡二叉树
树上的任一结点的左子树和右子树的高度之差不超过1
特点:有更高的搜索效率
顺序存储
常考的基本操作(非完全二叉树转换为完全二叉树)
i的左孩子 2i
i的右孩子 2i+1
i的父节点 i/2 (向下取整)
i所在的层次 (log2(n+1)) log2n +1(向下取整)
若完全二叉树中共有n个结点,则(对于非完全二叉树不可以)
判断i是否有左孩子 2i<=n
判断i是否有右孩子 2i+1<=n
判断i是否有分支节点、叶子节点 i>n/2 向下取整
最坏情况下:高度为h且只有h个结点的单支树(所有结点只有右孩子),也至少需要2^h-1个存储单元
结论:二叉树的顺序存储结构,仅适用于存储完全二叉树
链式存储
//链式存储
struct ElemType{ //假设每个ElemType类型包含一个int型变量
int value;
};
//找到指定结点p的左右孩子(*******)
typedef struct BiTNode{
ElemType data; //数据域
struct BiTNode *lchild,*rchild; //左、右孩子指针域
}BiTNode,*BiTree;
//定义一颗空树
BiTree root = NULL;
//插入根节点
root = (BiTree) malloc(sizeof(BiTNode));
root->data = {1};
root->lchild = NULL;
root->rchild = NULL;
//插入新结点
BitNode *p = (BiTNode *)malloc(sizeof(BiTNode));
p->data ={2};
p->lchild = NULL;
p->rchild = NULL;
root->lchild = p; //作为根节点的左孩子
...
//如何找到指定结点p的父节点----(只能从根结点开始遍历)
// 解决办法:三叉链表,方便找父节点
typedef struct BiTNode{
ElemType data;
struct BiTNode *lchild,*rchild,*parent; //左右孩子,父结点指针
}BiTNode, *BiTree;
思考
(*****)n个结点的二叉链表共有n+1个空链域,其中这n+1个空链域可用于构造线索二叉树
如果结点从0开始编号????
二叉树的遍历
按照某种次序将所有结点都访问一遍
先序遍历:根左右
中序(根)遍历:左根右
后序(根)遍历:左右根
算术表达式的“分析树”
//先序遍历
typedef struct BiTNode{
ElemType data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
void PreOrder(BiTree T){
if(T!=NULL){
visit(T); //访问根节点
PreOrder(T->lchild); //遍历左子树
PreOrder(T->rchild); //遍历右子树
}
}
//中序遍历
void PreOrder(BiTree T){
if(T!=NULL){
PreOrder(T->lchild); //遍历左子树
visit(T); //访问根节点
PreOrder(T->rchild); //遍历右子树
}
}
//后序遍历
void PreOrder(BiTree T){
if(T!=NULL){
PreOrder(T->lchild); //遍历左子树
PreOrder(T->rchild); //遍历右子树
visit(T); //访问根节点
}
}
空间复杂度:O(h) 深度
//求树的深度(应用)
int treeDepth(BiTree T){
if(T == NULL)
return 0;
else {
int l = treeDepth(T->lchild);
int r = treeDepth(T->rchild);
//树的深度
return l>r ? l+1:r+1;
}
}
层次遍历
思路
①初始化一个队列
②根节点入队
③若队列不为空,先将队头元素出队,访问该结点,左右孩子插入队尾。
;//层次遍历
void levelOrder(BiTree T){
LinkQueue Q;
InitQueue(Q); //初始化辅助队列
BiTree p;
EnQueue(Q,T); //将根节点入队
while(!IsEmpty(Q)){ //队列不空则循环
DeQueue(Q,p); //队头结点出队
visit(p); //访问出队结点
if(p->lchild!=NULL)
EnQueue(Q,p->lchild); //左孩子入队
if(p->rchild!=NULL)
EnQueue(Q,p->rchild); //右孩子入队
}
}
//二叉树的结点(链式存储)
typedef struct BiTNode(){
ElemType data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
//链式队列的结点
typedef struct LinkList{
BiTNode *data; //存指针而不是存结点
struct LinkNode *next;
}LinkNode;
typedef struct{
LinkNode *front,*rear; //队头队尾
}
根据遍历序列来构造二叉树
前中
后中
层中
二叉树的线索化
树的存储结构
树的逻辑结构回顾
双亲表示法(顺序存储)
每个结点保存指向双亲结点的 “指针”
data parent
0 A -1
1 B 0
2 C 0
3 D 0
4 E 1
5 F 1
6 G 2
7 H 3
8 I 3
9 J 3
10 K 4
11 M 7
12 L 4
1. 新增数据元素,无需按逻辑上的次序存储
2. 删除元素
3. 优点:查指定结点的双亲很方便
缺点:查指定结点的孩子只能从头遍历
回顾:二叉树的顺序存储
二叉树的顺序存储中,一定要把二叉树的结点编号和完全二叉树对应起来
i的左孩子------2i
i的右孩子------2i+1
i的父结点------i/2 向下取整
结点编号不仅反映了存储位置,也隐含了结点之间的逻辑关系
// 双亲表示法
#define MAX_TREE_SIZE 100 //最大元素个数
typedef struct{ //树的结点定义
ElemType data; //数据元素
int parent; //双亲的位置域
}PTNode;
typedef struct{ //树的类型定义
PTNode nodes[MAX_TREE_SIZE]; //双亲表示
int n; //结点数
}PTree;
孩子表示法(顺序+链式)
data *firstchild
//孩子表示法(顺序+链式)
struct CTNode{
int child; //孩子结点在数组中的位置
struct CTNode *next; //下一个孩子
};
typedef struct{
ElemType data;
struct CTNode *firstChild; //第一个孩子
}CTBox;
typedef struct{
CTBox nodes[MAX_TREE_SIZE];
int n,r; //结点数和根的位置
}
孩子兄弟表示法(链式存储)
//树的存储,孩子兄弟表示法
typedef struct CSNode{
ElemType data; //数据域
struct CSNode *firstchild,*nextsibling; //第一个孩子和右兄弟指针(左右指针)
}CSNode,*CSTree;
//二叉树的结点(链式存储)
typedef struct BiNode{
ElemType data;
struct BiNode *lchild,*rchild;
}BiNode,*BiTree;
重要考点:树、森林、二叉树的转换
本质:用二叉链表存储森林------左孩子右兄弟
森林中各个树的根节点之间视为兄弟关系
树和森林的遍历
树的遍历:先根、后根、层序遍历(下述表格极其的重要)
树 森林 二叉树
先根 先根 先序
后根 后根 中序
//树的先根遍历
//树的先根遍历和对应二叉树的先序序列是相同的(*************)
void PreOrder(TreeNode *R){
if(R!=NULL){
visit(R); //访问根结点
while(R还有下一个子树T){
PreOrder(T); //先根遍历下一颗子树
}
}
}
//树的后根遍历
//树的后根遍历和对应二叉树的中序遍历是相同的
void PostOrder(TreeNode *R){
if(R!=NULL){
while(R还有下一个子树)
PostOrder(T); //后根遍历下一个子树
visit(R); //访问根结点
}
}
树的层次遍历(广度优先遍历) 使用队列实现
若队非空,则根节点入队
若队列非空,队头元素出队并访问,同时将该元素的孩子依次出队
重复上述步骤
森林的遍历:先序遍历、中序遍历
森林是m颗互不相交的树的集合。每棵树去掉根结点之后,其各个子树又构成森林。
1.先序遍历森林
等价于树的先根遍历 即等价于二叉树的先序遍历
2.中序遍历
等价于树的后根遍历 即等价于二叉树的先序遍历
哈夫曼树
带权路径长度
结点的权:有某种现实含义的数值
结点的带权路径长度:从树的根到该结点的路径长度(经过的边数)与该结点上权值的乘积
树的带权路径长度:树中所有叶结点的带权路径长度之和(WPL)
哈夫曼的定义
在含有n个带权叶子结点的二叉树中,其中带权路径长度最小的二叉树称为哈夫曼树,也称为最优二叉树。
哈夫曼树的构造
省略
哈夫曼编码
可变长度编码--允许对不同字符用不等长的二进制位表示
若没有一个编码是另外一个编码的前缀,则称这样的编码为前缀码
题目
5.2 二叉树的概念
14.一颗有124个叶子结点的完全二叉树,最多有 248 个结点
思路:
完全二叉树的 特点:若 i <= n/2 (向下取整),则结点i为分支结点,否则为分支结点。
完全二叉树最多只有一个度为1 的结点
N0 = N2 + 1
15.一颗有n个结点的二叉树采用二叉链表存储结点,其中空指针数为n+1
根节点 提供两个子结点,其余结点均占有一个结点,但又提供两个新的结点
故 n-1 + 2 = n+1
16.在一颗完全二叉树中,其根的序号是1 ,log2p = log 2q(向下取整)可判定序号为p和q的两个结点是否在同一层。
层数公式:log2 n + 1 向下取整。
应试技巧:排除法
17.假定一颗三叉树的结点数为50,则它的最小高度为5
思路:记公式啦logm [n(m-1)+1]
或者
高度为h的满三叉树的个数 (3^h -1)/2
18.已知一颗有2011个结点的树,其叶子结点个数为116,该树对应的二叉树中无右孩子的结点个数是
思路:树转换为二叉树:
规则 左孩子右兄弟
19. 对于一个满二叉树 ,共有n个结点和m个叶子结点,高度为h,则
思路:n = 2^h - 1
m = 2^(h-1)
2m -1 = n (注意当不给h的条件时, 需要创造h的中间变量,去得到m和n之间的关系)
20. 已知一颗完全二叉树的第6层(根为第一层)有8个叶结点,则该完全二叉树的结点个数最多为111
思路:对于完全二叉树第i层有多少个结点的问题:
两种情况:一、高度为i,至多只有这么些结点
二、高度为i+1,第i+1层有结点占用了i层的结点
21. 若一颗完全二叉树有768个结点,则该二叉树中叶子结点的个数为:384
重要(*************************)
完全二叉树的性质:最后一个分支结点的序号为n/2 向下取整
(该性质在堆排序当中也涉及到了)
由以上的信息也可得出:叶子结点比非叶子结点数多1or 二者相等
22. 设一颗非完全二叉树T的所有叶子结点均位于同一层,且每个非叶子结点都有2个子结点,若T有k个叶结点,则T的结点总数2k-1
23. 对于任意一颗高度为5且有10个结点的二叉树,若采用顺序存储结构保存,每个结点占1个存储单元(仅存放结点的数据信息),则存放该二叉树需要的存储单元数量至少是31
注意:理解至少和最少的含义
至少:对于任意一颗二叉树都要满足条件
24.在一颗完全二叉树中,含有N0个叶子结点,当度为1 的结点数为1时,该树的高度为多少? 当度为1的结点数为0时,该树的高度为?
n0 = n2 + 1
25.一颗有n个结点的满二叉树有多少个分支结点和多少个叶子结点?该满二叉树的高度为?
26.已知完全二叉树的第九层有240个结点,则整个完全二叉树有多少个结点?有多少个叶子结点?
27.略
28.略
5.3 二叉树的遍历和线索二叉树
1.若有一个叶子结点是二叉树中某个子树的中序遍历结果序列的最后一个结点,则它一定是该子树的前序遍历结果序列的最后一个结点。
思路:根左右、左根右
2.在任何一颗二叉树中,如果结点a 有左孩子b和右孩子c,则在结点的先序序列、中序序列、后序序列中,结点b一定在结点a前面。
3.设n和m为一颗二叉树的两个结点,在中序遍历中,在m的前面的条件是 n在m的左方
4.同上,在后序遍历中,n在m前面的条件是 n在m的左方
5.(???)在二叉树中有两个结点m和n,如果m是n的祖先,使用 后序遍历 可以找到m到n的路径
6.在二叉树 的前序序列、中序序列、后序序列中,所有叶子结点的先后顺序 完全相同
7.对二叉树的结点从1编号,要求根> 左、右 左<右,可采用后续遍历
思路:左<右<根
8.前序为ABC,后序为CBA 的二叉树共有4种
穷举法
注意:寻找前序和后序中相同的结点的方式,将n++,最后2^n.
9.(****)一颗非空的二叉树的先序和后序刚好相反,则该二叉树一定满足只有一个叶子结点
所有结点均无(左)右孩子
另外一种说法:高度等于其结点数
总结:是先序和后序,不是中序
10.设结点x和y是中任意的两个结点,先序x在y前,后序x在y后,则x和y的关系 x为y的祖先
11.如果二叉树中的先序为a……b ,后序为b……a,则结点b在结点a的左子树中
先序:根左右
中序:左根右
根左
12.一颗二叉树的先序为1234567,则中序可能为1234567·
新思路:按照前序序列为入栈顺序,后序序列为出栈顺序
13.不能唯一确定一颗二叉树的是 先序和后序
先中、后中、层中
14.后序:DABEC 中序:DEBAC,先序CEDAB
15.先序:ABCDEF 中序:CBAEDF 后序:CBEFDA
16.层次ABCDEF 中序:BADCFE 先序:ABCDEF
17.引入线索二叉树的目的加快查找前驱和后继结点的速度
线索二叉树:利用二叉树中空余的指针存放线索,线索是前驱结点和后继结点的指针
18 线索二叉树是一种物理结构
注意: 二叉树是一种逻辑结构
线索二叉树 指明了在存储过程中的数据存放方式
19.n个结点的线索二叉树上含有的线索数为 n+1
2n-(n-1)
20.(****)判断线索二叉树中*p结点有右孩子结点的条件是
注意:使用tag来标识存放的地址是左/右孩子的地址还是前驱/后继的地址
0为默认值,则表示一般情况下有孩子
21.(*****)一颗左子树为空的二叉树在先序线索二叉树后,其中 空的链域的个数是 2
注意:一颗任意的二叉树,也任意使用先中后序线索化,则线索化后的空链域最多有?
线索化的定义:若某结点的某指针为空,则空的左指针用于存放上一个结点的地址,
空的右指针存放下一个结点的地址
①首结点的左指针,先序的首结点为根节点,左子树为空, 左指针为空
②尾结点的右指针:先序的尾结点为叶子结点,所以右指针为空
22.(**)略。。。。 线索二叉树
23.(*****)二叉树在线索化后,仍然不能解决的问题
后序线索二叉树中求后序后继
24.(****)若x是二叉中序线索树中一个有左孩子的结点,且x不为根,则x的前驱为 X的左子树的右结点
25.(*****???) 后序线索树 的遍历需要栈的支持
思考:前序和中序为什么不需要?
普通的二叉树,不论是递归算法,还是非递归算法都是需要栈的支持的。
因为使用了线索二叉树
26.同9
27.略,反方向问题 ,问遍历方式
28.略,后序线索二叉树的定义,会画线索树
29.(*****) 先序:1234 后序:4321
中序可能为1234 2341 4321
结论:两个结点,在前序和后序中的前后关系不同,证明二者是父子关系,如果相同,则为兄弟关系。
注意上述的思路
30.前序:aebdc 后序bcdea,则根结点的孩子结点
分析:前序,则a为根结点,e为a的左/右孩子,
从后序中,当e为左孩子的时候,一定没有右孩子,
当e为右孩子时,由先序,可知,a一定没有左孩子的
所以只有e为孩子结点
31.若x是后续线索二叉树中的叶结点,且x存在左兄弟结点y,则x的右线索指向x的根结点。
注意审题
32.略,中序线索化
33. 先序序列为abcd的不同二叉树的个数为
结论一:(1/n+1) C 2n n
结论二:前序序列和中序序列的关系,是以前序序列为入栈次序,中序序列为出栈顺序。
34.(新题型)二叉树如图所示, 后序序列为eacbegf,与a同层的为d
35.要使一颗非空二叉树的先序和中序相同,其所有非叶子结点需要满足的条件为只有右子树
36.若某非空二叉树的先序和后序相反,则该二叉树的形态是什么样的?
右根 左根
37 若先序和后序相同
仅含根节点
38.(*******????)编写后序遍历二叉树的非递归算法
39.(****)试给出二叉树的自下而上,从右到左的层次遍历算法
思路:二叉树的层次遍历,是利用队列的方式,对二叉树进行操作
而题目中是反向操作,则我们可以将队列中的元素出队到栈中,栈中元素再出栈则可以得到如题所示的层次遍历。
40.假设二叉树采用二叉链表存储结构,设计一个非递归算法求二叉树的高度。
非递归算法:先中后层
41.设一颗
5.4树、森林
1.完全二叉树中,若一个结点没有左孩子,则它必是叶子结点
2.(*****) 利用二叉链表存储森林,则根节点的右指针是不一定为空
左孩子右兄弟
森林可能包含1颗或更多
3. 设森林中有三颗树,第一、点二、第三颗树的结点个数为m1,m2,m3,与森林F对应的二叉树根结点的右子树上的结点个数是m2+m3
4. 设森林对应的二叉树为b,它有m个结点,b的根为p,p的右子树结点个数为n,森林中第一颗树的结点个数m-n
森林中的根结点为第一颗树的根结点
5.略
6.(*****)设f是一个森林,b是由f变换而来的二叉树,若f中有n个非终端结点,则b中右指针域为空的结点有 2 个
翻译:森林中有n个非终端结点,问有多少结点没有兄弟?
15.
16. 森林f对应的二叉树为t,f的先根为abcdef,中根为badfec,则T的后根为 bfedca
17. 若森林对应的二叉树为T,若T的先序遍历为abdcegf,中序为bdaegcf,则f中树的棵树为 3