关于树

某次面试

-数据结构怎么样

-emmm,还行吧

-讲讲红黑树吧

-寄

只会对着板子抄线段树QAQ

总结一下二叉树相关吧

二叉排序树 (二叉搜索树/BST)

左子树的所有节点 < 根节点 < 右子树的所有节点 ( ls_max < r < rs_min )

它的左右子树也分别为二叉搜索树

没有相同的值

二叉搜索树的插入

空树,就首先生成根节点;

不是空树就按照查找的算法,找到父节点,然后作为叶子节点插入,如果值已经存在就插入失败

二叉搜索树的删除

如果删除的是叶节点,可以直接删除

image-20220918171608352

如果被删除的元素有一个子节点,可以将子节点直接移到被删除元素的位置

image-20220918172101889

如果有两个子节点,这时候就采用中序遍历,找到待删除的节点的后继节点(或前驱节点),将其与待删除的节点互换,此时待删除节点的位置已经是叶子节点,可以直接删除

image-20220918172300705|image-20220918172506128

插入删除复杂度

平均复杂度 $log_2N$

最坏情况: n ( 单链)

二叉搜索树的中序遍历为从小到大排序结果

哈希表与二叉查找树

哈希表中的数据是无序存储的,如果要输出有序数据序列,需要先进行排序,或者配合有序链表来使用。而对于二叉查找树,我们只需要进行中序遍历,就可以在O(n)的时间复杂度内,输出有序数据序列。

哈希表扩容耗时很多,而且当遇到哈希冲突时,性能不稳定。而对于二叉查找树,如果用平衡二叉树就非常稳定,时间复杂度稳定在O(logn)

O(logn)的算法并不一定比O(1)的算法运行速度慢。尽管哈希表上操作的时间复杂度是常量级的,但因为哈希冲突的存在,再加上哈希函数的计算耗时,哈希表并不一定就比平衡二叉树效率高。

哈希表的构造比二叉查找树复杂,需要考虑的东西很多,如哈希函数的设计、冲突解决方法、扩容和缩容等。平衡二叉树只需要考虑如何维护平衡性

平衡二叉树 (AVL树)

在二叉排序树的基础上,有

左子树和右子树的高度之差的绝对值小于等于1

左子树和右子树也是平衡二叉树

为了方便起见,给树上的每个结点附加一个数字,给出该结点左子树与右子树的高度差,这个数字称为结点的平衡因子(BF)

平衡因子=结点左子树的高度-结点右子树的高度

平衡因子 = {-1,0,1}

为什么会有平衡二叉树

因为二叉排序树在某些情况下的效率会达到 $o(n)$ ,这明显不是我们想要的结果 (链表不爽啊吗)

有了|BF| <= 1这个限制条件以后,就可以将复杂度控制在 $log_2N$ 左右了

左旋和右旋

左旋|右旋

左旋

右儿子变父亲

右儿子的左子树变父亲的右子树

右旋

左儿子变父亲

左儿子的右子树变左子树

平衡二叉树插入和调整

四种失衡类型

失衡是相对于平衡而言的,在插入新的节点后,会导致新树在原来的基础上失衡,这时就需要调整了

找最小的失衡子树的根节点作为失衡结点

最小失衡子树:距离插入节点最近的,且平衡因子的绝对值大于1的节点为根的子树

LL

在原来平衡的二叉树上,在节点的左子树的左子树下插入节点导致失衡

image-20220918200009070

对节点进行右旋

image-20220918200045913
LR

原来平衡的二叉树上,在节点的左子树的右子树下插入节点导致失衡

image-20220918200329007

先对左儿子进行左旋,这时树就变为LL,然后对节点进行右旋

image-20220918200826766
RR

在原来平衡的二叉树上,在节点的右子树的右子树下插入节点导致的失衡

image-20220918201508730

对节点进行左旋

image-20220918201558230
RL

在原来平衡的二叉树上,在节点的右子树的左子树下插入节点导致的失衡

image-20220918201928419

先对右节点进行右旋,这时变为RR,再对节点左旋

image-20220918202132662

平衡二叉树删除节点

删除的步骤跟二叉排序树的删除差不多,删完后不断向上检查是否失衡,失衡就调整,直到根节点

红黑树

红黑树是一种特化的AVL树

与AVL树相比,红黑树牺牲了部分平衡性,以换取插入/删除操作时较少的旋转操作,整体来说性能要优于AVL树

红黑树通过变色和旋转来维护

红黑树特征

5大特征:

每个节点不是黑色就是红色

根节点为黑色

红色节点的父节点和子节点不能为红色 (不能有相邻的红色节点)

所有的叶子节点 (最底层不存放数据的节点) 都是黑色, 且为空

每个节点到叶子节点的每个路径黑色节点的个数都相等

为什么会有红黑树

AVL的左右子树高度差不能超过1,每次进行插入/删除操作时,几乎都需要通过旋转操作保持平衡
在频繁进行插入/删除的场景中,频繁的旋转操作使得AVL的性能大打折扣
红黑树通过牺牲严格的平衡,换取插入/删除时少量的旋转操作,整体性能优于AVL
红黑树插入时的不平衡,不超过两次旋转就可以解决;删除时的不平衡,不超过三次旋转就能解决
红黑树的红黑规则,保证最坏的情况下,也能在 $o(log_2N)$ 时间内完成查找操作

红黑树和AVL树

AVL树的查找效率更高, 但平衡调整的成本也更高

在需要频繁查找时, 选用AVL树

在需要频繁插入删除时,选择红黑树

红黑树的维护

插入

插入节点默认为红色

插入节点为根节点

将节点变为黑色

image-20221028224228266

插入节点的父节点为黑色

此时红黑树性质没有被打破,不用管

image-20221028224316039
插入节点的父节点和叔节点都是红色

由于父节点和叔节点都是红色,所以爷爷一定是黑色

BD两个红色节点连在一起了,所以将A的黑色”下放”一层

即A变红,B,C变黑,这样就保证BC的路径上黑色节点数不变了

image-20221028224510848|image-20221028225002044

插入节点的父节点为红色,叔节点为黑色或没有叔叔

父节点为红色,爷爷一定为黑色

插入节点为左孩子

以爷爷为轴右旋,这时D路径少一个黑色,所以将A的黑色上一层

image-20221028225528332 image-20221028225758682
插入节点为右孩子
image-20221028225324026

对插入节点左旋,变为上面的情况

image-20221028225444298

删除

写在前面~~~~的废话

红黑树的删除比较复杂, 网上的教程讲的也不大致相同, 先按照我觉得自己可以理解的版本来写, 等我觉得我能完全讲清楚了再把这个重构一下

有两个儿子的节点, 将其后继节点的内容赋值给当前节点, 并删除其后继节点

对于没有儿子的节点, 只需要删除当前节点

对于有一个儿子的节点, 直接删除该节点, 并用其儿子节点代替该节点

对于有两个儿子的节点, 将其后继节点的内容赋值给当前节点, 并删除其后继节点

由树的性质可知后继节点没有儿子或者只有一个右儿子

考虑下面的例子, 需要删除8节点, 找到它的后继节点10, 并将10的值赋给8, 此时由于只改变了值而没有改变颜色, 所以树的性质不变

image-20221030195316556

接下来考虑删除10号节点

image-20221030195537611

此时10号节点可以等价考虑为一个只有右子树的节点

考虑删除一个只有右子树节点的情况

自身为红色, 子节点为黑色

image-20221030195903141

已知删除和插入红色节点都不影响红黑树的性质, 所以直接删除即可

自身为黑色, 子节点为红色

image-20221030200010860

删除自己,然后把儿子涂黑

image-20221030200049583

自身为黑色,儿子也为黑色

image-20221030200254375

这种情况比较复杂, 因为不能通过向下修改来维护树的平衡

所以要向上递归修改

B树

又叫b-树, 类似于普通的平衡二叉树, 但是允许每个节点有更多的子节点

b树是专门为外部存储器设计的, S