基本概念
Q: 什么是度?
度就是节点拥有子节点的个数。
Q: 什么是叶子节点
度为O的节点
Q: 什么是二叉树
每个节点的的度最多为2.就是最多有两个子树。且有序
Q: 什么是真二叉树
节点的度要不为0 要不为2。(没有度为1的节点)
Q: 什么是满二叉树
节点的度要不为0 要不为2。所有叶子节点都在最后一层。
Q: 什么是完全二叉树
叶子节点只会出现在最后两层。且最后一层像左对齐
满二叉树一定是真二叉树
满二叉树一定是完全二叉树。
二叉树
如图。是一个二叉树。可以看得出来他是一个有序树。他的左子树比又子树小
在我们面试的过程中有些问题
-
什么是二叉树。
-
说一下二叉树前序遍历、中序遍历、后续遍历的区别。
- 前序遍历。先遍历根节点,然后遍历左子树,右子树。最常见的可以使用递归遍历
func orderTree(Node *node) { if node == nil return; printf(node.element) ordertree(node.left) ordertree(node.right) }- 中序遍历。 左子树 根节点 右子树 或者 右子树 根 左 (中序遍历出来的数据 从小到大排列的。或者从大到小排列的)
if node == nil return; ordertree(node.left) printf(node.element) ordertree(node.right)- 后序遍历。 根节点放在最后遍历。
-
写一个反转二叉树的算法。
反转二叉树的算法完全可以基于遍历来写。例如:
func orderTree(Node *node) { if node == nil return; Node temp = node.left; node.left = node.right; node.right = temp ordertree(node.left) ordertree(node.right) }
二叉搜索树
任意节点。他所有左子树。小于所有右子树。
如果在一个动态整数中,查找一个整数
使用有序动态数组。虽然可以用2分查找 O(logn)。但是插入很慢 O(n)。 这个时候可以用搜索二叉树
他可以提高搜索效率。 但是他存储的元素必须有可比性。如果存储对象,必须指定可比方式。
- 他的添加,删除,查找复杂度为。O(h)和他的高度紧密相关。 也可以写成 O(logn)
AVL树
如果搜索二叉树的添加顺序是 从小往大添加的。 那他就会退化成链表类似的结构。 为了防止搜索二叉树退化成链表。 所以平衡二叉树登场。
-
两边子树的高度差的越小。 越平衡。
-
我们是没有办法控制数据添加和删除的顺序的。能做的是在添加和删除之后。使用尽量少的调整,适度的提高树的两边平衡。
-
但是调整的次数也不能太多,太多的话反而增加时间复杂度。应该是适当的调整,达到适度的平衡即可
-
一个达到适度平衡的二叉搜索树,叫做 平衡二叉搜索树 (AVL 红黑树 自平衡的二叉搜索树)
那么AVL树就是根据平衡因子来确保。二叉搜索树不会降低为链表,从而损失该有的性能。
那什么是平衡因子呢。 其实就是两个子树的高度差。 AVL平衡因子的 每个平衡因子 只能是 1,0 , -1 所以他的 搜索添加删除的复杂度 都是 O(logn)
- 当给AVL添加元素的时候时候,如果平衡因子大于1或者小于-1的时候, 他会通过旋转来保持平衡。
总结:
-
AVL的添加元素,可能所有祖先节点都失衡。 但是只要调整最低的失衡点,就全部恢复平衡
-
AVL删除元素。 会导致被删除的父节点或者祖先节点失去平衡 。 父节点平衡之后,可能会导致更高层的节点失衡。所以要全部恢复平衡才行 logn次
-
平均时间复杂度。
搜索 O(logn)
添加 O(logn) 他的旋转是 O(log1)
删除 O(logn) 但是他的 旋转也是 O(log1)
B树
B树不一定是二叉树。 是一个平衡的多路搜索树。多用于文件系统,数据库的实现
- 一个节点可以存储超过2个元素、可以拥有超过2个子节点
- 拥有二叉搜索树的一些性质
- 平衡、每个节点的所有子树高度一致
- 比较矮
红黑树
**由于线性数据结构在搜索的时候存在一定瓶颈,所以有了树状结构来解决。线性结构的搜索效率一般都是O(n). 如果是有序的话(二分查找是logN 、但是删除和添加负责度也是On) 二叉搜索树的 logN次。 **
AVL树是为了防止二叉搜索树退化成链表的。所有有了 自平衡二叉树
AVL树缺点是在删除的时候,可能会有logN次的调整。所以有了红黑树,红黑树的添加删除都是O1级别来的
性质
也是一种自平衡的二叉搜索树。也可以叫他为平衡二叉B树
红黑树和4阶B树是等价的
添加
由于 红黑树和B树等价的性质,在由于B树添加的节点都是在叶子节点。 所以推论如下
1、一共添加分为12中情况
2、 四种父节点为黑色。 则直接添加不需要调整,他可以完全满足红黑树五条性质
3、八种需要修改。 他不能满足性质4 (八种里面。前四种添加之后不会上溢)
(后四种情况他的叔父节点是红色。 前面四种是黑色(null 也是黑哦))
-
LL\RR 情况。 parent染成black 。 grand染成red 然后 grand 旋转
-
LR\RL 如上图如果加的是48 和 74 。 则染色是一直的。 只不过是需要旋转两次。 和AVL一样。
- 上溢 - LL / 上溢 - RR 两种类似
-
上溢 - LR / 上溢 - RL . 都和上面的类似就不截图了 。 代码如下
if(isRed(uncle)) { black(parend); blacl(grand); affteradd(red(grand)); }
删除
由于红黑树和B树等价。 所以删除红黑树肯定是删除他的子节点。 有下面八种情况。
- 其中四种是删除红色节点, 红色节点的删除对原二叉树性质没有影响,所以直接删除
- 但是25的话是不可能被删除的。 因为删除25的时候会找他的前驱或者后继来覆盖他。 那其实就是删除17、33、就是这样理解的
- 所以就剩下 46、76、88 . 三种情况
- 46 和 76 的判断条件是 替代他的节点是红色。 删除他们也很简单, 就是帮他删除然后替代他的染成黑色。 入下图2.。
-
如果删除图1 的88 节点的话。 则有一个问题就是 B树 下溢。解决下溢两种八法
-
1、找兄弟节点借 。 如下图、 如果可以借的话。则有两个条件。 兄弟节点是黑色,切兄弟节点有红色子节点
-
2、兄弟是黑色,但是他没有红色子节点,那他就不能借了。 就找父节点下来合并
如果父节点是红色。直接下来就可以。 如果是黑色,则父节点也会下溢。那就帮父节点当成新的要删除的节点来处理就可以。如下图
- 3、兄弟是红色。则如下图。 让兄弟节点的儿子成为自己的兄弟节点。 然后在删除
AVL 的平衡是靠平衡因子。 平衡因子 1,0 , -1
红黑树怎么保证平衡的 ?
1、红黑树的五条性质,可以保证红黑树等价四阶B树。 从B树的角度来说B树是非常平衡的。
他能保证,最大路径最多是最短路径的两倍。因为最大路径和最短路径的黑色节点相同,最多多出一倍的红色节点。
AVL 是强平衡。 但是红黑树是弱平衡。没有那么的平衡。
集合
特点
- 不存放重复的元素。
- 没有索引
- 常用于存储新增的东西。(新增IP等)
- 去重
底部封装
内部可以,用动态数据、链表, 红黑树,等数据结构来实现
- 使用链表封装
查找 On
删除 On
添加 On
- 使用红黑树封装
搜索 O(logn)
添加 O(logn)
删除 O(logn)
对比可知。 如果大数据操作。红黑树性能要比链表高的多。 二叉树本来就是去重的功能
但是红黑树也是有限制的。 就是元素必须要有可比性,如果没有可比性就没有办法加进去。
那就使用什么呢 ? ~~~
映射
Map 在有些语言里面叫做字典 (Python,OC,Swift)
底部可使用链表。 或者红黑树来实现。 但是链表的性能比较差。 所以使用红黑树。
红黑树的节点,包含key value。 key 作为可比较的值。
缺点和集合一样。 他的key 要有可比较性。 而且查找 containsValue 比较麻烦必须要遍历查找。 所以是On 级别的
如果用value 作为可比较,那查找key就会比较麻烦
Java官方的 TreeMap 用的就是红黑树
Map和Set的关系
如果Map 去掉 value 就是一个Set。
iOS 的 Set 和 dict 不一定是红黑树, 他没有开源。 但是我想不是吧,因为红黑树节点要有可比性。
复杂度
添加、删除、搜索 都是 logN。 但是value
特点
key 有可比较性
元素分布有顺序。 前序遍历可以安顺序遍历的出来。
但是在实际开发过程中,很多key是不需要顺序的。
而且 key 是不需要有可比较性的。
这个时候,用TreeMap 是不是有点浪费了 ?
不考虑顺序,不考虑Key的,可比较性,Map是否有更好的实现方案,平均时间复杂度可以达到O1 的?
那就使用Hash表来实现吧。