文章目录
树的基本概念
树是一种递归定义的数据结构
祖先结点、子孙结点、双亲结点(父节点)、孩子结点、兄弟结点、堂兄弟结点
有序树——逻辑上看,树中结点的各子树从左至右是有次序的,不能互换
无序树——逻辑上看,树中结点的各子树从左至右是无次序的,可以互换
森林:森林是m(m≥0)棵互不相交的树的集合
常考性质
根节点没算到总度数里
三叉树是每个结点最多三个孩子,可以没有这种结点(只是个小于的意思,没有也可以是三叉树)
好推的很,画一下就知道了
用等比数列算
常见考点5(至少有多少个):
高度为h的m叉树至少有 h 个结点。(一根直线)
高度为h、度为m的树至少有 h+m-1 个结点。(一根直线最下面一层m个结点)
不用记,现场推就行了,反正向上取整(高度最小就是全部排满)
二叉树
1.基本概念
二叉树是有序树
2.几个特殊的二叉树
满二叉树就是全满
完全二叉树是一个一个挨着来的
比如此时完全二叉树14那里连一个就不是完全二叉树了,没有一个一个挨着来
完全二叉树中如果某结点只有一个孩子,那么一定是左孩子
平衡二叉树:树上任一结点的左子树和右子树的深度之差不超过1。平衡二叉树能使搜索的效率更高,长得胖胖的
3.二叉树常考性质
常见考点2:二叉树第 i 层至多有 2i-1 个结点(i≥1)
常见考点3:高度为h的二叉树至多有 2ℎ − 1个结点(满二叉树)
4.完全二叉树的常考性质
至少:h-1层满再加一个
已知n,找一个h满足2h-1 ≤ n ≤ 2h-1(左边是第h行起始的个数,右边是这一行能达到最多的个数)
突破点:完全二叉树最多只会有一个度为1的结点
只需给出n就可以得到n0,n1,n2
n1=0或1,n0=n2+1,设n0为k求解即可(由于n0
+n2一定为奇数,所以可以根据n的奇偶性判断n1的值)
二叉树的存储结构
1.顺序存储
为了表示结点之间的关系,树的编号得和完全二叉树的对应,所以顺序存储这样有可能浪费不少空间
结论:二叉树的顺序存储结构,只适合存储完全二叉树
2.链式存储
n个结点的二叉链表共有 n+1 个空链域(n个结点有2n个域,除了根结点都被一根线连着,总共用n-1个域,所以空出n+1个域)
链式存储很容易找到左右孩子,但是很难找父亲结点(需要从根遍历)
再增加一个父结点指针,就可以轻松找到父结点了
小结
二叉树前中后序遍历
分支结点逐层展开法
如先序:先写ABC,再展开成A(BDE)(CFG)
就是一种递归的实现,把左右访问的看成新的树继续遍历
先序遍历——第一次路过时访问结点
中序遍历——第二次路过时访问结点
后序遍历——第三次路过时访问结点
二叉树层序遍历
由遍历序列构造二叉树
若只给出一棵二叉树的前/中/后/层序遍历序列中的一种,不能唯一确定一棵二叉树
1.前序+中序
从前序遍历序列可以看出孩子结点是谁,从中序可以分出左右
2.后序+中序
3.层序+中序
找到树的根节点,并根据中序序列划分左右子树,再找到左右子树根节点
其他的组合都是不能确定一棵二叉树的
线索二叉树
1.中序线索二叉树
n个结点,有n+1个空链域,可以用来记录前驱、后继的信息
空的拿来装线索,最前面最后面指向NULL,其他的不用管
2.先序线索二叉树
3.后序线索二叉树
小结
手算出遍历顺序之后,对着画出线索树就行
二叉树的线索化
1.中序线索化
遍历二叉树的时候线索化
把建立线索的操作放到访问结点的函数里,pre指向当前结点的前驱,如果当前结点无左子树。就用建立前驱线索指向pre,如果pre的结点没有右子树,则建立后继线索指向当前结点,最后再把pre指向当前结点,继续访问
2.先序线索化
如果不加这个判断的话,D建立前驱线索B,下一条就直接访问B,然后B又访问D,循环……所以得判断,在不是前驱线索的时候才访问左孩子
3.后序线索化
小结
本质上就是遍历,只是在访问结点的时候增加了建立前驱后继的操作(先序有一点不同,要判断),最后再处理最后一个结点
线索二叉树找前驱后继
这里面都是没有装线索的结点(也就是他们左右都指向孩子结点),探讨他们如何找到前驱后继
1.中序二叉树
①后继
右子树最左下角的
②前驱
左子树右下角
2.先序二叉树
①后继
②前驱
除了从头遍历,不然找不到
此时如果采用三叉链表还是可以的,毕竟可以直接找到父结点
③左兄弟非空的情况下,右孩子的前驱结点是左兄弟里右下角那个(前驱结点是左兄弟先序遍历最后一个结点)
3.后序二叉树
①后继
也一样,后继肯定在自己上一层那里,直接是找不到的,还是得靠三叉链表找到父结点
③右兄弟第一个被后序遍历,所以是右兄弟左下角那个
②前驱
小结
树的存储结构
1.双亲表示法
0放根节点,-1表示没有双亲,其他的依次来就行,指针指向父结点
新增数据元素,无需按逻辑上的次序存储。直接在后面接上就可以
删除:①把里面指针设为-1 ②用新的覆盖
优点:查双亲结点很快
缺点:查孩子结点很慢,因为是到处放的,只能从头遍历(看某个的双亲结点是不是指定结点),里面还有空数据导致遍历速度降低(设为-1的)
2.孩子表示法
每个结点的孩子形成一个链,指针指向链的第一个结点
3.孩子兄弟表示法
把树转化成二叉树,连左边就是父子关系,右边就是兄弟关系
树和二叉树的转换
4.森林和二叉树的转换
转换一手,就可以用二叉树来存储森林了
开始的时候把各树的根节点看成兄弟就行了,和树转换成二叉树的方法一样
小结
树、森林的遍历
1.树的遍历
①先根遍历
先根遍历,先访问根结点然后依次访问各个子树
②后根遍历
③层次遍历
2.森林的遍历
①先序遍历
依次对各个树进行先根遍历
效果等同于对转换的二叉树的先序遍历
②中序遍历
依次对各个树进行后根遍历
效果等同于依次对二叉树的中序遍历
二叉排序树
1.查找
2.插入
3.删除
①若被删除结点z是叶结点,则直接删除,不会破坏二叉排序树的性质。
②若结点z只有一棵左子树或右子树,则让z的子树成为z父结点的子树,替代z的位置。
把左边最大或右边最小的弄出来(60弄到50那,其他补上就行,30弄过去也行)
4.查找效率
①查找成功
最好O(log2n),也就是每次可以去掉一半,折半查;最坏O(n),一个一个查完
平衡二叉树。树上任一结点的左子树和右子树的深度之差不超过1。
②查找失败
查找失败要补上失败结点,才能准确算出有多少种情况(失败了也分比最后那个大还是小),最后一次比较不相等就算查找失败了,不需要多算一下
小结
平衡二叉树
树上任一结点的左子树和右子树的高度之差不超过1。
结点的平衡因子=左子树高-右子树高。
平衡二叉树结点的平衡因子的值只可能是−1、0或1。
1.插入
插入之后有可能打破平衡,所以要调整
平衡因子都算出来,找到第一个不平衡的结点,把它的子树调整好就ok
①左孩子的左子树(LL)
②右孩子的右子树(RR)
③左孩子的右子树(LR)
左孩子的右子树,就把那个右子树的根节点弄到顶上去
④右孩子的左子树(RL)
LL和RR,把孩子结点弄上去
LR和RL,把子树的根节点弄到顶上去(弄到第一个不平衡的结点那里)
在插入操作中,只要将最小不平衡子树调整平衡,则其他祖先结点都会恢复平衡
练习一手:
整完检查
2.查找效率
平衡二叉树的查找效率为O(log2n)
小结
哈夫曼树
1.带权路径长度
结点的权:有某种现实含义的数值(如:表示结点的重要性等)
结点的带权路径长度:从树的根到该结点的路径长度(经过的边数)与该结点上权值的乘积
树的带权路径长度:树中所有叶结点的带权路径长度之和(WPL, Weighted Path Length)
2.构造
1)每个初始结点最终都成为叶结点,且权值越小的结点到根结点的路径长度越大
2)哈夫曼树的结点总数为2n − 1(n0=n,没有度为1的结点,n0=n2+1)
3)哈夫曼树中不存在度为1的结点。(肯定都连满了,路径才最短)
4)哈夫曼树并不唯一,但WPL必然相同且为最优
固定长度编码——每个字符用相等长度的二进制位表示
可变长度编码——允许对不同字符用不等长的二进制位表示
若没有一个编码是另一个编码的前缀,则称这样的编码为
前缀编码
哈夫曼树不唯一,因此哈夫曼编码不唯一
哈夫曼编码可以用来压缩数据