B树和红黑树

B树和红黑树给定一个假想出来的节点都是黑色节点 这是为了让这棵树变成真二叉树 即是要么度为 0 要么度为 2 懵逼了 先学 B 树 解释一下 为什么说它拥有二叉搜索树的一些性质 看 3 阶 B 树 比 23 小的都在左边 处于比 23 大但比 30 小的在中间 比 30 大的在右边 还有一点

大家好,我是讯享网,很高兴认识大家。


讯享网

给定一个假想出来的节点都是黑色节点

这是为了让这棵树变成真二叉树  

即是要么度为0 要么度为2

懵逼了。。。。。。。。

先学B树:

解释一下:为什么说它拥有二叉搜索树的一些性质?

看3阶B树:

比23小的都在左边  处于比23大但比30小的在中间

比30大的在右边 

还有一点:

这个m的意思就是这个树的节点最多拥有m个子节点  

向上取整和向下取整的符号:

1.这是向上取整

 2.这是向下取整

 

数据库中的B树:一般是200到300阶左右

 把二叉搜索树某些父子节点合并就可能会得到一颗B树

 

58比40大那么去右边  比60小那么去左边

发现比50 55大 那么排到55后面

 我们前面性质可知:

4阶B树最多一个节点接入3个节点

当我们插入98时 最后一个段变成了4个节点  那么会导致上溢  

当插入34发现满了之后上溢

那么我们把这一行节点的中间节点给它合并到父节点处

若当还是上溢的时候

同理把中间元素40与上面父节点合并即可

删除:

 其实真正被删除的还是叶子节点中的节点

删除可能会产生下溢:

 下溢的处理方法:

上溢会使B树高度增加

下溢会使B树变矮

4阶B树: 

 

红黑树:

这棵不是红黑树:

因为不满足性质:因为我们红黑树会模拟一个虚拟节点是假设的  是不存在的

但是我们在找路径的时候 也要找这一条路径

 

 红黑树代码基础架构:

package BinaryTree; import java.util.Comparator; public class RBTree<E> extends BST<E>{ / * 红黑树需要染色 */ private static final boolean RED=false; private static final boolean BLACK=true; public RBTree() { this(null); } public RBTree(Comparator<E> comparator) { super(comparator); } / * 创建一个染色的方法 封装起来 */ private Node<E> color(Node<E> node,boolean color){ if(node==null){ return null; } ((RBNode<E>)node).color=color; return node; } / *染色成红色 黑色 */ private Node<E> red(Node<E> node){ return color(node,RED); } private Node<E> black(Node<E> node){ return color(node,BLACK); } / * 传进来一个节点 判断是什么颜色 */ private boolean colorOf(Node<E> node){ //如果是null那么在红黑树中默认是BLACK颜色 //不为空 那么就看看它到底是什么颜色 return node==null?BLACK:((RBNode<E>)node).color; } / *你是否为黑色? */ private boolean isBlack(Node<E> node){ return colorOf(node)==BLACK; } / *你是否为红色? */ private boolean isRed(Node<E> node){ return colorOf(node)==RED; } / *开创一个内部类 */ private static class RBNode<E> extends Node<E> { boolean color; public RBNode(E elements, Node<E> parent) { super(elements, parent); } } } 

讯享网

在BinaryTree中:

讯享网 / *为了写红黑树 * 我们要寻找它的兄弟节点 sibling */ public Node<E> sibling(){ //当前节点是父节点的左子树时 //兄弟节点一定是父节点的右子树 if(isLeftChild()){ return parent.right; } if(isRightChild()){ return parent.left; } return null; }

  红黑树根节点必然是黑色

当我们添加一个元素的时候

把红黑树看成一个B树

B树中我们只能添加到叶子节点  在红黑树中 我们只可添加到最后一排

一共有2+2+1+2+2+1+2=12种情况

 前面四种添加不用做任何处理 也就是当父节点是黑色时

后面八种添加要进行相应的处理

这八种我们分类处理:

第一类:

 当添加的是52  60这两个红色节点的时候

依照性质:不可以有两个节点连续是红色

举个实例:添加52这个节点:

第一步:把50染成黑色  46染成红色

第二步:对46进行左旋转操作  因为增加的节点是RR

之后更新父关系 让38指向50

 这一种添加是LR 或RL

还是拿一个实例进行举例:

当我们添加节点48的时候:

首先把48染成黑色  然后把46染成红色

为什么?因为在对parent进行单旋 对grand也进行旋转之后  48会成为根节点与38相连

38为红色 因此48为黑色

因此两个子节点应该是红色

uncle节点:叔父节点;即是父节点的兄弟节点

ok:

以上两类添加情况:叔父节点都不是红色  即是黑色

举个例子:48的叔父节点是46左子节点为null

我们假设为是一个黑色的空结点

 因此第三类与前一类的分界条件:叔父节点是否为红色

第二类: :uncle是RED

 

 其实这里我们图省略了一开始的80这个红色节点

当25上去之后  25 38 55 80发生上溢

那么38先上去并且38染成黑色  38成为根节点

注意:

继承关系

 我们再创建一个BBST存放旋转的操作  并且让BBST继承BST

  因为AVL和RBTree都存在旋转的操作  并且让AVL RBTree继承BBST

核心代码:

重写addafter:

 / *重写添加之后的操作 */ @Override//node这里是添加的元素 protected void addafter(Node<E> node) { Node<E> parent = node.parent; //添加的是根节点 或者递归调用上溢到了根节点 if (parent == null) { black(node); return; } //如果父节点是黑色 那么直接返回即可 if (isBlack(parent)) { return; } //叔父节点 Node<E> uncle = parent.sibling(); //祖父节点 Node<E> grand = parent.parent; if (isRed(uncle)) { black(parent);//把parent染成黑色 black(uncle);//叔父同 red(grand);//祖父染成red色 染色完成之后当作新添加的节点向上合并 addafter(grand);//递归进行 直到添加完成 return; } //叔父节点不是红色 //一下注释的代码 表示是存在重复的 // 可以简洁写出来成一句放在前面即可 if (parent.isLeftChild()) {//L red(grand); if(node.isLeftChild()){//LL black(parent); //red(grand); //rotateright(grand); } else {//LR black(node); //red(grand); rotateleft(parent); //rotateright(grand); } rotateright(grand); } else {//R red(grand); if(node.isLeftChild()){//RL black(node); //red(grand); rotateright(parent); //rotateleft(grand); } else {//RR black(parent); //red(grand); //rotateleft(grand); } rotateleft(grand); } }

 这个代码这样写更加清晰:

讯享网 //叔父节点不是红色 if (parent.isLeftChild()) {//L if(node.isLeftChild()){//LL black(parent); red(grand); rotateright(grand); } else {//LR black(node); red(grand); rotateleft(parent); rotateright(grand); } } else {//R if (node.isLeftChild()) {//RL black(node); red(grand); rotateright(parent); rotateleft(grand); } else {//RR black(parent); red(grand); rotateleft(grand); } } }

删除:

红色节点直接删除 不用做任何处理:

这个是removeafter的代码 也就是删除之后操作的代码

所以删除之后直接不用管了  都已经继承了其他类中的删除了已经 

删除黑色节点:

第一种有两个red红色节点的  假如说删除25

那么就是相当于删除两边的红色节点 但是红色节点的删除是不用考虑的

因此不用管这种情况

把第二种情况拉出来分析:

只需要把这个黑色节点旁边的红色节点染成黑色

然后让80指向这个被染成黑色的节点 就相当于删除了这个拥有一个RED子节点的BLACK节点 

因此我们删除之后的操作只需要把代替者染成黑色即可 如代码所示

 在红黑树 用颜色作为判定条件 :

 当删除之后替代节点也是红色 那么重复了

那么就要把替代节点染成黑色即可

ok:

我们现在开搞删除黑色叶子节点:

一:黑黑兄弟

——————————————————————————————————————————

重点;

我们删除的时候能借兄弟节点的基础是:

sibling(兄弟节点) 是一个黑色节点 并且至少有一个红色的子节点

不符合这个规定肯定不可以直接借

——————————————————————————————————————————

删除节点的兄弟是黑色的情况  

sibling 表示兄弟节点

 假如删除节点88 那么一定会导致B树节点下溢

如果兄弟节点有多的节点 那么就可借走一个

重点;

我们删除的时候能借兄弟节点的基础是:

sibling(兄弟节点) 是一个黑色节点 并且至少有一个红色的子节点

删除的是 黑色叶子节点  具体情况如下:

 第一组:

 当删除88时

产生下溢:那么78上去  80再下来 

其实也就是一个旋转操作

先对76 左旋  

再对 80右旋

中心节点这里是78 我们也要让中心节点继承parent的颜色

并且两个子节点的颜色要染成黑色

 

第二组:

同样删除88 

这一次同样产生下溢 

然后 这种是LL 那么只需让80右旋转即可

那么中心节点即是76

中心节点继承parent节点的颜色

并且两个子节点的颜色要染成黑色

第三组:

当出现这种情况的时候 删除88 依然会导致下溢

通过观察兄弟节点 可知:LL或LR

我们有两种做法   :

但是我们旋转较简单的LL

把80进行右旋转 那么76就上去成为中心节点了

中心节点继承parent的颜色属性

红黑树只可以和四阶B树对应

不可以与其他阶的B树相提并论

 

情况一:

情况一:

 当兄弟节点无法借的时候  但一旦删除了88这个节点 产生下溢

我们需要把父节点80下来与76进行合并

情况二:

但如果父节点只有一个元素的时候

不可以把父节点拿下来合并 

这个时候直接套用之前的逻辑进行处理这种下溢情况

递归使用removeafter即可

 ok 到这里删除节点的兄弟是黑色的情况已经说完了

 二:红黑兄弟

删除的节点的兄弟是红色的情况

 为了好办:

我们把兄弟的子节点强制变成我的兄弟

举个例子:

88的兄弟是55  55的子是76

我们强制把76变成88的兄弟

 我们把80进行右旋w转 之后55变成80的父节点

80指向76 这时候76成为了88的兄弟节点

那么哈哈哈哈

就变成了我们之前讲过的情况了  黑兄弟回归!!

_____________________________________________________________________________

总结了一个旋转的规律 超开心:我发现的哈哈哈哈哈:

当我们把88这个节点删除之后会发生下溢现象  那么我们应该借用兄弟节点中的78

那么又为了维持 平衡二叉树的性质

左子<父<右子  因此我们最终应该保证78在中  76在左  80在右

第一步:

76到78是向右 那么先76左旋 

76左旋的意思就是成为没旋转之前的子节点78的左子节点

自己画一个图:此时:80指向78  78的左子树指向76

(注意树变化了  下面也要根据这个变化的来)

第二步:

80到76向左

那么80右旋  80右旋的意思就是成为没旋转前的子节点78的右子节点

 

当我们进行第一步左旋转76之后 我们让76成为78的左子节点

那么此时88的兄弟节点发生变化

变化成为了78而不是用来的76 

重写删除之后的操作代码:

 / *重写删除之后的操作 * 因为我们在AVL树中已经写过了删除的操作 * 因此我们这里只要重写红黑树中特殊的删除后的操作即可 */ @Override protected void removeafter(Node<E> node, Node<E> replacement) { //如果删除的节点是红色 if(isRed(node)){ return; } //用以取代的node的节点是红色 if(isRed(replacement)){ black(replacement); return; } Node<E> parent=node.parent; if(parent==null){//删除的是根节点 return; } //删除的是黑色叶子节点 //判断被删除的node是左还是右 因为我们这个是删除之后的操作 //到这的都是叶子节点 删除时操作已经让我们无法知道兄弟节点的方向了 boolean left=parent.left==null||node.isLeftChild(); Node<E> sibling=left?parent.right:parent.left; if(left){//当被删除的节点在左边 兄弟节点在右边 if(isRed(sibling)){ black(sibling); red(parent); rotateleft(parent); sibling=parent.right; } if(isBlack(sibling.left)&&isBlack(sibling.right)){ boolean parentBlack=isBlack(parent); black(parent); red(sibling); if(parentBlack){ removeafter(parent,null); } else { color(sibling,colorOf(parent)); isBlack(parent); isBlack(sibling.left); rotateright(parent); } } } else {//当被删除的节点在右边 兄弟节点在左边 if(isRed(sibling)) {//兄弟节点是红色 black(sibling); red(parent); rotateright(parent); //更换兄弟即可 sibling=parent.left; } //到这里的情况肯定是兄弟节点必然是黑色 if(isBlack(sibling.left)&&isBlack(sibling.right)) { //这种情况就是兄弟节点没有子节点或者有红色子节点 // 即是它只有虚拟黑色节点或红色子节点 我们默认虚拟的不存在的节点是黑色的而已 //因为这是最后一层 这是B树的特性 boolean parentBlack = isBlack(parent); black(parent); red(sibling); if (parentBlack) { //如果parent是黑色的节点 我们需要做的就是把下溢后的空结点删除 removeafter(parent, null); } } else {//兄弟节点至少有一个红色子节点 那么就可以向兄弟节点借用元素了 //如果兄弟节点左边是黑色 那么先进行旋转 if(isBlack(sibling.left)){ rotateleft(sibling); //每一次旋转之后兄弟节点都会变化 sibling=parent.left; } //把中心节点的颜色染成和父节点颜色一致 color(sibling,colorOf(parent)); black(sibling.left); black(parent); rotateright(parent); } } } 

RBTree:红黑树总代码:

讯享网package BinaryTree; import java.util.Comparator; public class RBTree<E> extends BBST<E>{ / * 红黑树需要染色 */ private static final boolean RED=false; private static final boolean BLACK=true; public RBTree() { this(null); } public RBTree(Comparator<E> comparator) { super(comparator); } / *重写添加之后的操作 */ @Override//node这里是添加的元素 protected void addafter(Node<E> node) { Node<E> parent = node.parent; //添加的是根节点 或者递归调用上溢到了根节点 if (parent == null) { black(node); return; } //如果父节点是黑色 那么直接返回即可 if (isBlack(parent)) { return; } //叔父节点 Node<E> uncle = parent.sibling(); //祖父节点 Node<E> grand = parent.parent; if (isRed(uncle)) { black(parent);//把parent染成黑色 black(uncle);//叔父同 red(grand);//祖父染成red色 染色完成之后当作新添加的节点向上合并 addafter(grand);//递归进行 直到添加完成 return; } //叔父节点不是红色 if (parent.isLeftChild()) {//L if(node.isLeftChild()){//LL black(parent); red(grand); rotateright(grand); } else {//LR black(node); red(grand); rotateleft(parent); rotateright(grand); } } else {//R if (node.isLeftChild()) {//RL black(node); red(grand); rotateright(parent); rotateleft(grand); } else {//RR black(parent); red(grand); rotateleft(grand); } } } / *重写删除之后的操作 * 因为我们在AVL树中已经写过了删除的操作 * 因此我们这里只要重写红黑树中特殊的删除后的操作即可 */ @Override protected void removeafter(Node<E> node, Node<E> replacement) { //如果删除的节点是红色 if(isRed(node)){ return; } //用以取代的node的节点是红色 if(isRed(replacement)){ black(replacement); return; } Node<E> parent=node.parent; if(parent==null){//删除的是根节点 return; } //删除的是黑色叶子节点 //判断被删除的node是左还是右 因为我们这个是删除之后的操作 //到这的都是叶子节点 删除时操作已经让我们无法知道兄弟节点的方向了 boolean left=parent.left==null||node.isLeftChild(); Node<E> sibling=left?parent.right:parent.left; if(left){//当被删除的节点在左边 兄弟节点在右边 if(isRed(sibling)){ black(sibling); red(parent); rotateleft(parent); sibling=parent.right; } if(isBlack(sibling.left)&&isBlack(sibling.right)){ boolean parentBlack=isBlack(parent); black(parent); red(sibling); if(parentBlack){ removeafter(parent,null); } else { color(sibling,colorOf(parent)); isBlack(parent); isBlack(sibling.left); rotateright(parent); } } } else {//当被删除的节点在右边 兄弟节点在左边 if(isRed(sibling)) {//兄弟节点是红色 black(sibling); red(parent); rotateright(parent); //更换兄弟即可 sibling=parent.left; } //到这里的情况肯定是兄弟节点必然是黑色 if(isBlack(sibling.left)&&isBlack(sibling.right)) { //这种情况就是兄弟节点没有子节点或者有红色子节点 // 即是它只有虚拟黑色节点或红色子节点 我们默认虚拟的不存在的节点是黑色的而已 //因为这是最后一层 这是B树的特性 boolean parentBlack = isBlack(parent); black(parent); red(sibling); if (parentBlack) {//如果parent是黑色的节点 我们需要做的就是把下溢后的空结点删除 removeafter(parent, null); } } else {//兄弟节点至少有一个红色子节点 那么就可以向兄弟节点借用元素了 //如果兄弟节点左边是黑色 那么先进行旋转 if(isBlack(sibling.left)){ rotateleft(sibling); //每一次旋转之后兄弟节点都会变化 sibling=parent.left; } //把中心节点的颜色染成和父节点颜色一致 color(sibling,colorOf(parent)); black(sibling.left); black(parent); rotateright(parent); } } } / * 创建一个染色的方法 封装起来 */ private Node<E> color(Node<E> node,boolean color){ if(node==null){ return null; } //染色 ((RBNode<E>)node).color=color; return node; } / *染色成红色 黑色 */ private Node<E> red(Node<E> node){ return color(node,RED); } private Node<E> black(Node<E> node){ return color(node,BLACK); } / * 传进来一个节点 判断是什么颜色 */ private boolean colorOf(Node<E> node){ //如果是null那么在红黑树中默认是BLACK颜色 //不为空 那么就看看它到底是什么颜色 return node==null?BLACK:((RBNode<E>)node).color; } / *你是否为黑色? */ private boolean isBlack(Node<E> node){ return colorOf(node)==BLACK; } / *你是否为红色? */ private boolean isRed(Node<E> node){ return colorOf(node)==RED; } / *开创一个内部类 */ private static class RBNode<E> extends Node<E> { boolean color; public RBNode(E elements, Node<E> parent) { super(elements, parent); } } } 

总结:

红黑树和4阶B树之间的对应关系:

添加: 

 

前四种情况我们添加红色节点添加到黑色节点 这种删除之后不用管即可

但是后面八种情况添加的节点与原来节点组成了双红节点

因此这是不符合红黑树和4阶B树的性质的   我们要进行删除后的处理

 删除的时候我们只用关注最后一层即可:

情况1:

 情况2:

1.删除拥有2个RED子节点的BLACK节点不用做删除后的操作

2.这种用替代法

3.删除BLACK叶子节点

(1)

(2)

(3)

 

 

 当我们要删除55时

先找到前驱或者后继节点 然后代替它 

然后删除这个前驱或者后继节点

总结一句话:我们要删除的节点一定是在最后一层的

 红黑树的平衡:

 高度只要不太夸张的高(如链表 高度过于夸张)就被视为平衡

红黑树与AVL树相同:

在add  remove操作之后 我们需要进行之后的操作

 总而言之:红黑树优于AVL树

其实:

二叉树,不过如此。。。一开始确实感觉难  ‘但是一定要坚持。。。。

二叉树学完了,感谢相遇。。。。

小讯
上一篇 2025-03-17 09:07
下一篇 2025-01-09 09:09

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/44516.html