文章目录
树的定义和基本术语
树的基本概念
树形逻辑结构的应用
结点之间的关系描述
- 两个结点之间的路径是单向的,只能从上往下,且路径长度代表的含义是经过了几条边
结点和树的属性描述
有序树 v.s. 无序树
树 v.s. 森林
树的性质
- 除了根结点外,每个结点头上都会有一个“天线”,所以
结点数 = 总度数 + 1
二叉树的定义和基本术语
二叉树的基本概念
二叉树的五种形态
几个特殊的二叉树
满二叉树 v.s. 完全二叉树
二叉排序树
平衡二叉树
二叉树的性质
二叉树的常考性质
完全二叉树的常考性质
- 一个是根据两层满二叉树数量夹逼得来,一个是根据同一层的完全二叉树数量夹逼得来
- 完全二叉树中度为1的结点 n1 只能有0个或者1个,而且根据上面的结论,n0 = n2+1 我们可以得到 n0 + n2 = 2n2 +1一定是奇数
- 若完全二叉树的结点总数为偶数,我们可以知道度为1的结点n1的数量为1;若完全二叉树的结点总数为奇数,我们可以知道度为1的结点n1的数量为0;
二叉树的存储结构
二叉树的顺序存储
- 此时不能用结点的总数判断左右结点,但是我们可以通过结点的
isEmpty
字段来判断
- 此时会造成存储空间的大量浪费
二叉树的链式存储
- n个结点,每个结点都会有左右两个孩子指针,所以n个结点会有2n个指针域
- 但是n个结点由于根节点的原因只会有n-1个结点上有指针相连
- 所以n个结点的二叉链表共有n+1个空链域【构造线索二叉树】
二叉树的遍历
什么是遍历
二叉树的遍历
- 对算术表达式的分析书进行先序遍历,可以得到这个算术表达式的前缀表达式
- 对算术表达式的分析书进行中序遍历,可以得到这个算术表达式的中缀表达式(需要加界限符)
- 对算术表达式的分析书进行后序遍历,可以得到这个算术表达式的后缀表达式
先序遍历
- 可以在
visit函数
中添加其他逻辑完成更复杂的功能
先序遍历的函数调用关系
- 空间复杂度为O(h+1),h为二叉树的高度,+1是因为最下面的叶子结点还有两个空的左右子树也需要把它们压到栈顶
中序遍历
后序遍历
求遍历序列
- 红色箭头:第一次经过结点
- 绿色箭头:第二次经过结点
- 紫色箭头:第三次经过结点
- 前序遍历要求第一次经过结点的时候就访问该结点
- 中序遍历要求第二次经过结点的时候就访问该结点
- 后序遍历要求第三次经过结点的时候就访问该结点
求先序遍历序列
求中序遍历序列
求后序遍历序列
遍历算法的应用举例
- 求树的深度
二叉树的层序遍历
由遍历序列构造二叉树
不同二叉树的中序遍历序列
不同二叉树的前序遍历序列
不同二叉树的后序遍历序列
不同二叉树的层序遍历序列
由遍历序列构造二叉树
前序+中序遍历序列
- 前序序列当中最先出现的结点肯定是根节点,这样的话我们就可以确定根节点在中序遍历序列的位置
- 那么在中序序列中根节点左边出现的这些节点肯定就是左子树中的结点,右边出现的结点肯定就是右子树中的结点
- 我们就可以根据这两条结论递归地构造出二叉树
后序+中序遍历序列
层序+中序遍历序列
若前序、后序、层序两两组合?
线索二叉树
二叉树的中序遍历序列
- 此时q指向的结点就是p所指向的结点,那么此时pre结点所指向的就是p结点的中序遍历后继
- 如果要找的是p结点的中序遍历后继,我们可以把代码再执行一步,直到pre指向的结点就是p所指向的结点,那么此时q结点所指向的就是p结点的中序遍历后继
中序线索二叉树
- 找节点的前驱后继变得更方便了,遍历也变得更方便了
线索二叉树的存储结构
中序线索二叉树的存储
先序线索二叉树
先序线索二叉树的存储
后序线索二叉树
后序线索二叉树的存储
三种线索二叉树的对比
二叉树的线索化
用土办法找到中序前驱
中序线索化
先序线索化
- 先序线索化由于访问并且修改完根节点之后再遍历左右子树结点,可能会出先“爱的魔力转圈圈”的问题,即访问完根节点之后我们需要访问左子树,但是左子树此时为空且在我们访问根节点的时候已经把左孩子线索化为了先序前驱,所以会陷入死循环,当我们访问完一个节点之后又会去访问它的前驱,不断访问这两个结点
后序线索化
- 后序线索化当中肯定不会出现先序线索化中出现的转圈的问题,原因在于当我们在访问一个结点q的时候,这个结点它的左孩子那条路我们肯定已经处理完了,右孩子那条路我们肯定也已经处理完了,所以我们处理完之后不可能再回头去访问其左孩子所指向的那棵子树
- 只有先序线索化才会出现“爱的魔力转圈圈我”问题,原因在于当我们在visit一个结点q的时候,这个结点q的左子树,也就是左孩子指针所指的这条路肯定在之前就已经被遍历且处理过了
线索二叉树找前驱/后继
中序线索二叉树找中序后继
- 如果p结点已经被线索化了,那么p->rchild就是它的中序后继
- 如果p结点没有被线索化则证明p结点一定有右孩子或者右子树,根据中序遍历的规则,此时p结点的中序后继就是其右孩子或者右子树中最左下角的结点
中序线索二叉树找中序前驱
- 如果p结点已经被线索化了,那么p->lchild就是它的中序前驱
- 如果p结点没有被线索化则证明p结点一定有左孩子或者左子树,根据中序遍历的规则,此时p结点的中序前驱就是其左孩子或者左子树中最右下角的结点
先序线索二叉树找先序后继
- 如果p结点已经被线索化了,那么p->rchild就是它的先序后继
- 如果p结点没有被线索化则证明p结点一定有右孩子或者右子树,根据先序遍历的规则,此时p结点的先序后继会有两种情况:
- 若p点有左孩子,根据先序遍历的规则,此时的先序后继就是左孩子或者左子树的根节点(也是p的左孩子)
- 若p点没有左孩子,根据先序遍历的规则,此时的先序后继就是右孩子或者是右子树的根节点(也是p的右孩子)
先序线索二叉树找先序前驱
- 如果p结点已经被线索化了,那么p->rchild就是它的先序后继
- 但是如果p结点没有被线索化则证明p结点一定有右孩子或者右子树,根据先序遍历的规则,此时p的左子树和右子树都只能是p的先序后继,而不可能是p结点的先序前驱,因此我们不可能在p的左右子树中找到p结点的先序前驱,只能用土办法从头开始重新进行一次先序遍历
- 但是其实我们也可以把二叉链表改造为三叉链表,每个结点都会有指向其父节点的指针,此时又会出现4种情况:
此时我们应该优先地往右边走,如果右边的结点没有了那我们就应该优先地往左边走,以此类推找到最下面一层的结点
后序线索二叉树找后序前驱
- 如果p结点已经被线索化了,那么p->lchild就是它的后序前驱
- 如果p结点没有被线索化则证明p结点一定有左孩子或者左子树,根据后序遍历的规则,此时p结点的后序前驱会有两种情况:
- 若p点有右孩子,根据后序遍历的规则,此时的后序前驱就一定是它右子树当中按照后序遍历最后一个被访问到的结点,也就是p的右孩子
- 若p点没有右孩子,根据后序遍历的规则,此时的后序前驱就一定是左子树当中按照后序遍历最后一个被访问的结点,显然就是它的左孩子
后序线索二叉树找后序后继
- 如果p结点已经被线索化了,那么p->rchild就是它的后序后继
- 但是如果p结点没有被线索化则证明p结点一定有右孩子或者右子树,根据后序遍历的规则,此时p的左子树和右子树都只能是p的后序前驱,而不可能是p结点的后序后继,因此我们不可能在p的左右子树中找到p结点的后序后继,只能用土办法从头开始重新进行一次先序遍历
- 但是其实我们也可以把二叉链表改造为三叉链表,每个结点都会有指向其父节点的指针,此时又会出现4种情况:
-
-
-
按照后序遍历的规则此时p结点的后序后继就是其右兄弟子树当中按照后序遍历的规则第一个被访问的结点,也就是我们从根结点出发尽可能地往左走,如果往左地路没了但是还有往右的路的话,那我们还要继续往右走,用这种方式找到最下面一个叶子结点,就是第一个被后序遍历的结点 -
-
树的存储结构
树的逻辑结构
双亲表示法(顺序存储)
- 双亲表示法删除数据元素方案一:把删除元素结点的双亲指针设为-1,表示这个位置是空的
- 双亲表示法删除数据元素方案二【better】:把尾部的数据移上来填补删除元素的这个空白,这样可以保证前面所有的存储单元都是有效的
- 注意删除操作后也要把
PTree.n
的值改为相应的数值 - 如果删除的数据元素不是叶子节点,证明我们要把以这个结点为根节点的整棵子树都删掉,我们需要找到这个节点的其他子孙节点,把它们都统统删除
孩子表示法(顺序+链式存储)
孩子兄弟表示法(链式存储)
森林和二叉树的转换
树和森林的遍历
树的先根遍历
树的后根遍历
树的层次遍历
森林的先序遍历
森林的中序遍历
哈夫曼树
带权路径长度
哈夫曼树的定义
哈夫曼树的构造
n
个结点,需要n-1
次的合并才可以全部合并完,每次合并会产生一个分支节点,所以哈夫曼树结点总数是2n-1
,非叶子节点数n-1
哈夫曼编码
并查集
漏网之鱼:逻辑结构——集合
用互不相交的树表示多个“集合”
- 使用“双亲表示法”来表示“并查集”的好处就是并和查的这两个操作实现起来都十分方便,查这个操作只需要一路往上找到根结点即可,并的这个操作就是要把其中一棵树的根节点让其指向另一颗树的根节点
并查集的存储结构
并查集的基本操作
并查集的代码实现——初始化
并查集的代码实现——并和查
时间复杂度分析
- 最坏的时间复杂度的时间开销和树的高度h相关,因此如果我们想要优化这个并查集的效率的话,我们可以在构造的时候尽可能让树长得“矮胖”点,而不是“瘦高”点
Union操作的优化
- 一种想法:我们可以让小树合并到大树上面,因为如果是大树合并到小树上的话,有可能会使合并后整棵树的树高h增加
- 现在问题来了,我们如何表示一棵树的大小呢?我们可以用根节点的绝对值表示树的结点总数
并查集的终极优化
拓展:Find操作的优化——压缩路径