转自:https://juejin.cn/post/6844903859974848520
AVL
AVL(平衡二叉树),它也是一种二分搜索树。
它的特点是每个节点的左右子树之差不超过1。在某种特殊的情况下,普通的二分搜索树可能退化为链表,例如加入的元素顺序为1,2,3,4,5。这个时候查询的效率会从O(logn)退化为O(n)。而我们解决这种特定的情况就需要采用平衡二叉树来解决这个问题。
AVL的定义
AVL的每个节点的左子树小于(大于)该节点,右子树大于(小于)该节点,每个节点的左右子树的深度之差不超过1。
节点的左右子树深度之差叫平衡因子。
如图:
AVL的操作
增删改查操作和二分搜索树类似,但是要多考虑的就是对节点的平衡考虑,如果一串数字的插入顺序为3,4,5。那么这棵树结构就会退化为一个链表。而这时候AVL就会对这个树进行旋转操作来达到平衡,所以,我们就知道旋转的操作会在增加,删除,修改这三个地方进行旋转。
旋转操作分为下面四种情况:
LL右单旋转:(左左子树,右旋处理)
如图,8的左子树已经退化为链表,并且5,8这两个节点不再平衡,这时我们先找到深度最深的不平衡节点5,对节点5进行LL旋转操作,在如图的这种情况下,得到右图的结构
RR左单旋转:(右右子树,左旋处理)
如图,当插入顺序为当插入顺序为8,3,10,13,15的时候,树的结构变成左边的样子,这时10节点和8节点已经不平衡,为了保持AVL的平衡,我们要对10节点进行RR旋转,如右图所示
LR先左后右:(左右子树,左右旋处理)
如图。5,8节点已经不平衡,这时要对5节点做平衡处理,首先将5进行RR左旋转,7的左节点也变为5的右节点。
这时7,8还是不平衡的,对8进行右旋转,8的右节点也变为8的左节点,如图。
RL先右后左:(右左子树,右左旋处理)
如左图,8,13节点不平衡,对13节点进行LL右旋转,得到右图
这时8,10是不平衡的,对8节点进行RR左旋转,得到右图。
以上就是保持平衡的方式。
插入
插入操作和平衡二叉树类似,不过在插入之后要保持树的平衡,针对以上四种情况保持。
删除
删除操作和平衡二叉树类似,在删除的时候当把子节点移到删除节点位置后也可能对树进行旋转保持平衡。
AVL的增删改查的平均时间复杂度都是O(logn),他比二分搜索树的好处在于他能够对树结构进行一个平衡,而不让他退化为链表 这里如何判断一个节点是否平衡呢?这里看一下AVL中每个节点的结构private class Node{ public K key; public V value; public Node left, right; public int height;// 节点的当前高度 }
判断一个节点是否平衡,即计算一个节点的平衡因子
private int getBalanceFactor(Node node){ if(node == null) return 0; //返回平衡因子,如果结果小于-1说明右倾;结果大于1说明左倾;结果在-1到1之间说明节点平衡 return node.left.height - node.right.height; }
总的来说增加和删除要比二分搜索树多考虑的就是增加和删除功能
2-3树
2-3树是一种绝对平衡树。它的节点的元素个数可以为1个或2个。 如图,就是一棵2-3树:2-3树中的2代表一个节点有两个孩子,3代表一个节点有三个孩子。
2-3树的操作
插入
这里结合一个例子来查看2-3树是如何实现绝对平衡的。
例如,现在我们要依次增加1,2,3,4,5,6,7这7个元素,如图
如图所示,下面一个步骤一个步骤的分析:
- 插入1,判断无根节点,直接将1封装为节点并设置为根节点
- 插入2,这时因为1节点没有孩子并且只有1节点,所以直接将2加入节点1中
- 插入3,和2节点一样,将3节点放入根节点中,这时根节点有3个元素了,就需要变化为步骤4的样子。可以理解成将1,2,3的中间元素提取成根节点,也就是将2提出来,1作为左孩子,3为右孩子
- 插入4,4比2大,增加到节点3
- 插入5,5比2大,增加到节点3,4中,这时节点3,4变为节点3,4,5,将节点3,4,5按照第三步将中间元素提取为双亲结点,而4提取出来的4回去找双亲节点2,2节点只有一个元素,所以4加入到2节点中
- 插入6,6大于根节点的2和4,进入最右边,5没有孩子只有一个元素,加入6到5节点
- 插入7,这时5,6,7将6提取出放入6的双亲结点,6的双亲节点(根节点)变为2,4,6。2,4,6提取出4变成最后的样子
总的来说,插入方法和二分搜索树相似。但是每个节点可以有1-2个元素,当节点元素个数为3时,就会分成3个节点并向上合并,直到合并完成。
查找
2-3树的查找和二分搜索树类似,不过因为一个节点可能有2个元素,需要对这两个元素进行比较,分别前往这个节点的左,中,右孩子继续进行比较
删除
2-3树的删除稍微复杂一点,删除可分为两大情况,就是删除叶子节点和非叶子节点。
这里只说理论情况,不结合代码实现,实际上代码实现会变得复杂也只是因为考虑的东西更多,代码实现会变得复杂。
删除叶子节点
- 当前节点是3节点:直接删除
- 当前节点是2节点:删除并判断
- 双亲是2节点,判断兄弟节点
- 兄弟节点是3节点,将兄弟节点移到双亲节点,再将双亲节点的另一个元素移动到当前节点
- 兄弟节点是2节点,先通过移动兄弟节点的中序遍历直接后驱到兄弟节点,以使兄弟节点变为3节点,再进行删除
- 双亲是3节点,拆分双亲节点使其成为2节点,再将再将双亲节点中最接近的一个拆分key与中孩子合并,将合并后的节点作为当前节点
- 双亲是2节点,判断兄弟节点
- 若2-3树是棵满二叉树,删除节点,将2-3树层树减少,并将兄弟节点合并到双亲节点中,同时将双亲节点的所有兄弟节点合并到双亲节点的双亲节点中,如果变为4节点,就做分解操作
删除非叶子节点
使用中序遍历下的直接后继节点key来覆盖当前节点key,再删除用来覆盖的后继节点key
了解完2-3树之后我们可以很轻松的了解和实现红黑树和B-树
红黑树
B树
B+树