【数据结构和算法05】 红-黑树(转发)

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

【数据结构和算法05】 红-黑树(看完包懂~)

置顶 2016年04月13日 15:50:25 eson_15 阅读数:52681 标签: java数据结构算法红黑树 更多

个人分类: ● 结构算法------【数据结构】

所属专栏: 数据结构和算法

 版权声明:尊重博主原创文章,转载请注明出处 https://blog.csdn.net/eson_15/article/details/51144079

【2018.6.2更新】我新搭建的博客系统上线了(使用SpringBoot搭建的),后面会在新系统中发表博客,这里也会给出链接,欢迎各位朋友收藏交流哈~ 
博客地址:http://www.itcodai.com       

 (友情提示,红-黑树是基于二叉搜索树的,如果对二叉搜索树不了解,可以先看看:二叉搜索树 )       

二叉搜索树是个很好的数据结构,可以快速地找到一个给定关键字的数据项,并且可以快速地插入和删除数据项。但是二叉搜索树有个很麻烦的问题,如果树中插入的是随机数据,则执行效果很好,但如果插入的是有序或者逆序的数据,那么二叉搜索树的执行速度就变得很慢。因为当插入数值有序时,二叉树就是非平衡的了,排在一条线上,其实就变成了一个链表……它的快速查找、插入和删除指定数据项的能力就丧失了。

为了能以较快的时间 O(logN) 来搜索一棵树,需要保证树总是平衡的(或者至少大部分是平衡的),这就是说对树中的每个节点在它左边的后代数目和在它右边的后代数目应该大致相等。红-黑树的就是这样的一棵平衡树,对一个要插入的数据项,插入例程要检查会不会破坏树的特征,如果破坏了,程序就会进行纠正,根据需要改变树的结构,从而保持树的平衡。那么红-黑树都有哪些特征呢?

1. 红-黑树的特征

它主要有两个特征: 1.节点都有颜色;2.在插入和删除的过程中,要遵循保持这些颜色的不同排列的规则。首先第一个特征很好解决,在节点类中店家一个数据字段,例如 boolean 型变量,以此来表示节点的颜色信息。第二个特征比较复杂,红-黑树有它的几个规则,如果遵循这些规则,那么树就是平衡的。红-黑树的主要规则如下:

  • 1.每个节点不是红色就是黑色的;

  • 2.根节点总是黑色的;

  • 3.如果节点是红色的,则它的子节点必须是黑色的(反之不一定);

  • 4.从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)。

在红-黑树中插入的节点都是红色的,这不是偶然的,因为插入一个红色节点比插入一个黑色节点违背红-黑规则的可能性更小。原因是:插入黑色节点总会改变黑色高度(违背规则4),但是插入红色节点只有一半的机会会违背规则3。另外违背规则3比违背规则4要更容易修正。当插入一个新的节点时,可能会破坏这种平衡性,那么红-黑树是如何修正的呢?

2. 平衡性的修正

红-黑树主要通过三种方式对平衡进行修正,改变节点颜色、左旋和右旋。这看起来有点抽象,我们分别来介绍它们。

2.1 变色

改变节点颜色比较容易理解,因为它违背了规则3。假设现在有个节点E,然后插入节点A和节点S,节点A在左子节点,S在右子节点,目前是平衡的。如果此时再插一个节点,那么就出现了不平衡了,因为红色节点的子节点必须为黑色,但是新插的节点是红色的。所以这时候就必须改变节点颜色了。所以我们将根的两个子节点从红色变为黑色(至于为什么都要变,下面插入的时候会详细介绍),将父节点会从黑色变成红色。可以用如下示意图表示一下:

2.2 左旋

通常左旋操作用于将一个向右倾斜的红色链接旋转为向左链接。示意图如下:

左旋有个很萌萌哒的动态示意图,可以方便理解:

.3 右旋

右旋可左旋刚好相反,这里不再赘述,直接看示意图:

当然咯,右旋也有个萌萌的动态图:

这里主要介绍了红-黑树对平衡的三种修正方式,大家有个感性的认识,那么什么时候该修正呢?什么时候该用哪种修正呢?这将是接下来我们要探讨的问题。

3. 红-黑树的操作

红-黑树的基本操作是添加、删除和旋转。对红-黑树进行添加或删除后,可能会破坏其平衡性,会用到哪种旋转方式去修正呢?我们首先对红-黑树的节点做一介绍,然后分别对左旋和右旋的具体实现做一分析,最后我们探讨下红-黑树的具体操作。

3.1 红-黑树的节点

红-黑树是对二叉搜索树的改进,所以其节点与二叉搜索树是差不多的,只不过在它基础上增加了一个boolean型变量来表示节点的颜色,具体看RBNode类:

public class RBNode<T extends Comparable<T>>{boolean color; //颜色T key; //关键字(键值)RBNode<T> left; //左子节点RBNode<T> right; //右子节点RBNode<T> parent; //父节点public RBNode(T key, boolean color, RBNode<T> parent, RBNode<T> left, RBNode<T> right) {this.key = key;this.color = color;this.parent = parent;this.left = left;this.right = right;}public T getKey() {return key;}public String toString() {return "" + key + (this.color == RED? "R" : "B");}
}

3.2 左旋的具体实现

上面对左旋的概念已经有了感性的认识了,这里就不再赘述了,我们从下面的代码中结合上面的示意图,探讨一下左旋的具体实现:

/*************对红黑树节点x进行左旋操作 ******************/
/** 左旋示意图:对节点x进行左旋*     p                       p*    /                       /*   x                       y*  / \                     / \* lx  y      ----->       x  ry*    / \                 / \*   ly ry               lx ly* 左旋做了三件事:* 1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)* 2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)* 3. 将y的左子节点设为x,将x的父节点设为y*/
private void leftRotate(RBNode<T> x) {//1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)RBNode<T> y = x.right;x.right = y.left;if(y.left != null) y.left.parent = x;//2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)y.parent = x.parent;if(x.parent == null) {this.root = y; //如果x的父节点为空,则将y设为父节点} else {if(x == x.parent.left) //如果x是左子节点x.parent.left = y; //则也将y设为左子节点elsex.parent.right = y;//否则将y设为右子节点}//3. 将y的左子节点设为x,将x的父节点设为yy.left = x;x.parent = y;        
}

3.3 右旋具体实现

上面对右旋的概念已经有了感性的认识了,这里也不再赘述了,我们从下面的代码中结合上面的示意图,探讨一下右旋的具体实现:

/*************对红黑树节点y进行右旋操作 ******************/
/** 左旋示意图:对节点y进行右旋*        p                   p*       /                   /*      y                   x*     / \                 / \*    x  ry   ----->      lx  y*   / \                     / \* lx  rx                   rx ry* 右旋做了三件事:* 1. 将x的右子节点赋给y的左子节点,并将y赋给x右子节点的父节点(x右子节点非空时)* 2. 将y的父节点p(非空时)赋给x的父节点,同时更新p的子节点为x(左或右)* 3. 将x的右子节点设为y,将y的父节点设为x*/
private void rightRotate(RBNode<T> y) {//1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)RBNode<T> x = y.left;y.left = x.right;if(x.right != null) x.right.parent = y;//2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)x.parent = y.parent;if(y.parent == null) {this.root = x; //如果x的父节点为空,则将y设为父节点} else {if(y == y.parent.right) //如果x是左子节点y.parent.right = x; //则也将y设为左子节点elsey.parent.left = x;//否则将y设为右子节点}//3. 将y的左子节点设为x,将x的父节点设为yx.right = y;y.parent = x;        
}

3.4 插入操作

分析完了红-黑树中主要的旋转操作,接下来我们开始分析常见的插入、删除等操作了。这里先分析插入操作。 由于红-黑树是二叉搜索树的改进,所以插入操作的前半工作时相同的,即先找到待插入的位置,再将节点插入,先来看看插入的前半段代码:

/*********************** 向红黑树中插入节点 **********************/
public void insert(T key) {RBNode<T> node = new RBNode<T>(key, RED, null, null, null);if(node != null) insert(node);
}//将节点插入到红黑树中,这个过程与二叉搜索树是一样的
private void insert(RBNode<T> node) {RBNode<T> current = null; //表示最后node的父节点RBNode<T> x = this.root; //用来向下搜索用的//1. 找到插入的位置while(x != null) {current = x;int cmp = node.key.compareTo(x.key);if(cmp < 0) x = x.left;elsex = x.right;}node.parent = current; //找到了位置,将当前current作为node的父节点//2. 接下来判断node是插在左子节点还是右子节点if(current != null) {int cmp = node.key.compareTo(current.key);if(cmp < 0)current.left = node;elsecurrent.right = node;} else {this.root = node;}//3. 将它重新修整为一颗红黑树insertFixUp(node);
}

这与二叉搜索树中实现的思路一模一样,这里不再赘述,主要看看方法里面最后一步insertFixUp操作。因为插入后可能会导致树的不平衡,insertFixUp方法里主要是分情况讨论,分析何时变色,何时左旋,何时右旋。我们先从理论上分析具体的情况,然后再看insertFixUp方法的具体实现。

如果是第一次插入,由于原树为空,所以只会违反红-黑树的规则2,所以只要把根节点涂黑即可;如果插入节点的父节点是黑色的,那不会违背红-黑树的规则,什么也不需要做;但是遇到如下三种情况时,我们就要开始变色和旋转了:

  • 1.插入节点的父节点和其叔叔节点(祖父节点的另一个子节点)均为红色的;

  • 2.插入节点的父节点是红色,叔叔节点是黑色,且插入节点是其父节点的右子节点;

  • 3.插入节点的父节点是红色,叔叔节点是黑色,且插入节点是其父节点的左子节点。

下面我们先挨个分析这三种情况都需要如何操作,然后再给出实现代码。

对于情况1:插入节点的父节点和其叔叔节点(祖父节点的另一个子节点)均为红色的。此时,肯定存在祖父节点,但是不知道父节点是其左子节点还是右子节点,但是由于对称性,我们只要讨论出一边的情况,另一种情况自然也与之对应。这里考虑父节点是祖父节点的左子节点的情况,如下左图所示:

对于这种情况,我们要做的操作有:将当前节点(4)的父节点(5)和叔叔节点(8)涂黑,将祖父节点(7)涂红,变成上右图所示的情况。再将当前节点指向其祖父节点,再次从新的当前节点开始算法(具体等下看下面的程序)。这样上右图就变成了情况2了。

对于情况2:插入节点的父节点是红色,叔叔节点是黑色,且插入节点是其父节点的右子节点。我们要做的操作有:将当前节点(7)的父节点(2)作为新的节点,以新的当前节点为支点做左旋操作。完成后如左下图所示,这样左下图就变成情况3了。

对于情况3:插入节点的父节点是红色,叔叔节点是黑色,且插入节点是其父节点的左子节点。我们要做的操作有:将当前节点的父节点(7)涂黑,将祖父节点(11)涂红,在祖父节点为支点做右旋操作。最后把根节点涂黑,整个红-黑树重新恢复了平衡,如右上图所示。至此,插入操作完成!

我们可以看出,如果是从情况1开始发生的,必然会走完情况2和3,也就是说这是一整个流程,当然咯,实际中可能不一定会从情况1发生,如果从情况2开始发生,那再走个情况3即可完成调整,如果直接只要调整情况3,那么前两种情况均不需要调整了。故变色和旋转之间的先后关系可以表示为:变色->左旋->右旋。

至此,我们完成了全部的插入操作。下面我们看看insertFixUp方法中的具体实现(可以结合上面的分析图,更加利与理解):

 private void insertFixUp(RBNode<T> node) {  RBNode<T> parent, gparent; //定义父节点和祖父节点  //需要修整的条件:父节点存在,且父节点的颜色是红色  while(((parent = parentOf(node)) != null) && isRed(parent)) {  gparent = parentOf(parent);//获得祖父节点  //若父节点是祖父节点的左子节点,下面else与其相反  if(parent == gparent.left) {                  RBNode<T> uncle = gparent.right; //获得叔叔节点  //case1: 叔叔节点也是红色  if(uncle != null && isRed(uncle)) {  setBlack(parent); //把父节点和叔叔节点涂黑  setBlack(uncle);  setRed(gparent); //把祖父节点涂红  node = gparent; //将位置放到祖父节点处  continue; //继续while,重新判断  }  //case2: 叔叔节点是黑色,且当前节点是右子节点  if(node == parent.right) {  leftRotate(parent); //从父节点处左旋  RBNode<T> tmp = parent; //然后将父节点和自己调换一下,为下面右旋做准备  parent = node;  node = tmp;  }  //case3: 叔叔节点是黑色,且当前节点是左子节点  setBlack(parent);  setRed(gparent);  rightRotate(gparent);  } else { //若父节点是祖父节点的右子节点,与上面的完全相反,本质一样的  RBNode<T> uncle = gparent.left;  //case1: 叔叔节点也是红色  if(uncle != null & isRed(uncle)) {  setBlack(parent);  setBlack(uncle);  setRed(gparent);  node = gparent;  continue;  }  //case2: 叔叔节点是黑色的,且当前节点是左子节点  if(node == parent.left) {  rightRotate(parent);  RBNode<T> tmp = parent;  parent = node;  node = tmp;  }  //case3: 叔叔节点是黑色的,且当前节点是右子节点  setBlack(parent);  setRed(gparent);  leftRotate(gparent);  }  }  //将根节点设置为黑色  setBlack(this.root);  
}  

 

4.完整源码

        终于分析完了插入和删除操作的所有东西。另外,红-黑树还有一些其他操作,比如:查找特定值、遍历、返回最值、销毁树等操作我将放到源码中给大家呈现出来,详见下面红-黑树的完整代码。

 

package tree;
/*** @description implementation of Red-Black Tree by Java* @author eson_15* @param <T>* @date 2016-4-18 19:27:28*/
public class RBTree<T extends Comparable<T>> {private RBNode<T> root; //根节点private static final boolean RED = false; //定义红黑树标志private static final boolean BLACK = true;//内部类:节点类public class RBNode<T extends Comparable<T>>{boolean color; //颜色T key; //关键字(键值)RBNode<T> left; //左子节点RBNode<T> right; //右子节点RBNode<T> parent; //父节点public RBNode(T key, boolean color, RBNode<T> parent, RBNode<T> left, RBNode<T> right) {this.key = key;this.color = color;this.parent = parent;this.left = left;this.right = right;}public T getKey() {return key;}public String toString() {return "" + key + (this.color == RED? "R" : "B");}}public RBTree() {root = null;}public RBNode<T> parentOf(RBNode<T> node) { //获得父节点return node != null? node.parent : null;}public void setParent(RBNode<T> node, RBNode<T> parent) { //设置父节点if(node != null) node.parent = parent;}public boolean colorOf(RBNode<T> node) { //获得节点的颜色return node != null? node.color : BLACK;}public boolean isRed(RBNode<T> node) { //判断节点的颜色return (node != null)&&(node.color == RED)? true : false;}public boolean isBlack(RBNode<T> node) {return !isRed(node);}public void setRed(RBNode<T> node) { //设置节点的颜色if(node != null) node.color = RED;}public void setBlack(RBNode<T> node) {if(node != null) {node.color = BLACK;}}public void setColor(RBNode<T> node, boolean color) {if(node != null)node.color = color;}/***************** 前序遍历红黑树 *********************/public void preOrder() {preOrder(root);}private void preOrder(RBNode<T> tree) {if(tree != null) {System.out.print(tree.key + " ");preOrder(tree.left);preOrder(tree.right);}}/***************** 中序遍历红黑树 *********************/public void inOrder() {inOrder(root);}private void inOrder(RBNode<T> tree) {if(tree != null) {preOrder(tree.left);System.out.print(tree.key + " ");preOrder(tree.right);}}/***************** 后序遍历红黑树 *********************/public void postOrder() {postOrder(root);}private void postOrder(RBNode<T> tree) {if(tree != null) {preOrder(tree.left);preOrder(tree.right);System.out.print(tree.key + " ");}}/**************** 查找红黑树中键值为key的节点 ***************/public RBNode<T> search(T key) {return search(root, key);
//        return search2(root, key); //使用递归的方法,本质一样的}private RBNode<T> search(RBNode<T> x, T key) {while(x != null) {int cmp = key.compareTo(x.key);if(cmp < 0) x = x.left;else if(cmp > 0) x = x.right;else return x;}return x;}//使用递归private RBNode<T> search2(RBNode<T> x, T key) {if(x == null)return x;int cmp = key.compareTo(x.key);if(cmp < 0)return search2(x.left, key);else if(cmp > 0) return search2(x.right, key);elsereturn x;}/**************** 查找最小节点的值  **********************/public T minValue() {RBNode<T> node = minNode(root);if(node != null)return node.key;return null;}private RBNode<T> minNode(RBNode<T> tree) {if(tree == null)return null;while(tree.left != null) {tree = tree.left;}return tree;}/******************** 查找最大节点的值 *******************/public T maxValue() {RBNode<T> node = maxNode(root);if(node != null)return node.key;return null;}private RBNode<T> maxNode(RBNode<T> tree) {if(tree == null)return null;while(tree.right != null)tree = tree.right;return tree;}/********* 查找节点x的后继节点,即大于节点x的最小节点 ***********/public RBNode<T> successor(RBNode<T> x) {//如果x有右子节点,那么后继节点为“以右子节点为根的子树的最小节点”if(x.right != null) return minNode(x.right);//如果x没有右子节点,会出现以下两种情况://1. x是其父节点的左子节点,则x的后继节点为它的父节点//2. x是其父节点的右子节点,则先查找x的父节点p,然后对p再次进行这两个条件的判断RBNode<T> p = x.parent;while((p != null) && (x == p.right)) { //对应情况2x = p;p = x.parent;}return p; //对应情况1}/********* 查找节点x的前驱节点,即小于节点x的最大节点 ************/public RBNode<T> predecessor(RBNode<T> x) {//如果x有左子节点,那么前驱结点为“左子节点为根的子树的最大节点”if(x.left != null) return maxNode(x.left);//如果x没有左子节点,会出现以下两种情况://1. x是其父节点的右子节点,则x的前驱节点是它的父节点//2. x是其父节点的左子节点,则先查找x的父节点p,然后对p再次进行这两个条件的判断RBNode<T> p = x.parent;while((p != null) && (x == p.left)) { //对应情况2x = p;p = x.parent;}return p; //对应情况1}/*************对红黑树节点x进行左旋操作 ******************//** 左旋示意图:对节点x进行左旋*     p                       p*    /                       /*   x                       y*  / \                     / \* lx  y      ----->       x  ry*    / \                 / \*   ly ry               lx ly* 左旋做了三件事:* 1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)* 2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)* 3. 将y的左子节点设为x,将x的父节点设为y*/private void leftRotate(RBNode<T> x) {//1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)RBNode<T> y = x.right;x.right = y.left;if(y.left != null) y.left.parent = x;//2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)y.parent = x.parent;if(x.parent == null) {this.root = y; //如果x的父节点为空,则将y设为父节点} else {if(x == x.parent.left) //如果x是左子节点x.parent.left = y; //则也将y设为左子节点elsex.parent.right = y;//否则将y设为右子节点}//3. 将y的左子节点设为x,将x的父节点设为yy.left = x;x.parent = y;        }/*************对红黑树节点y进行右旋操作 ******************//** 左旋示意图:对节点y进行右旋*        p                   p*       /                   /*      y                   x*     / \                 / \*    x  ry   ----->      lx  y*   / \                     / \* lx  rx                   rx ry* 右旋做了三件事:* 1. 将x的右子节点赋给y的左子节点,并将y赋给x右子节点的父节点(x右子节点非空时)* 2. 将y的父节点p(非空时)赋给x的父节点,同时更新p的子节点为x(左或右)* 3. 将x的右子节点设为y,将y的父节点设为x*/private void rightRotate(RBNode<T> y) {//1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)RBNode<T> x = y.left;y.left = x.right;if(x.right != null) x.right.parent = y;//2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)x.parent = y.parent;if(y.parent == null) {this.root = x; //如果x的父节点为空,则将y设为父节点} else {if(y == y.parent.right) //如果x是左子节点y.parent.right = x; //则也将y设为左子节点elsey.parent.left = x;//否则将y设为右子节点}//3. 将y的左子节点设为x,将x的父节点设为yx.right = y;y.parent = x;        }/*********************** 向红黑树中插入节点 **********************/public void insert(T key) {RBNode<T> node = new RBNode<T>(key, RED, null, null, null);if(node != null) insert(node);}//将节点插入到红黑树中,这个过程与二叉搜索树是一样的private void insert(RBNode<T> node) {RBNode<T> current = null; //表示最后node的父节点RBNode<T> x = this.root; //用来向下搜索用的//1. 找到插入的位置while(x != null) {current = x;int cmp = node.key.compareTo(x.key);if(cmp < 0) x = x.left;elsex = x.right;}node.parent = current; //找到了位置,将当前current作为node的父节点//2. 接下来判断node是插在左子节点还是右子节点if(current != null) {int cmp = node.key.compareTo(current.key);if(cmp < 0)current.left = node;elsecurrent.right = node;} else {this.root = node;}//3. 将它重新修整为一颗红黑树insertFixUp(node);}private void insertFixUp(RBNode<T> node) {RBNode<T> parent, gparent; //定义父节点和祖父节点//需要修整的条件:父节点存在,且父节点的颜色是红色while(((parent = parentOf(node)) != null) && isRed(parent)) {gparent = parentOf(parent);//获得祖父节点//若父节点是祖父节点的左子节点,下面else与其相反if(parent == gparent.left) {                RBNode<T> uncle = gparent.right; //获得叔叔节点//case1: 叔叔节点也是红色if(uncle != null && isRed(uncle)) {setBlack(parent); //把父节点和叔叔节点涂黑setBlack(uncle);setRed(gparent); //把祖父节点涂红node = gparent; //将位置放到祖父节点处continue; //继续while,重新判断}//case2: 叔叔节点是黑色,且当前节点是右子节点if(node == parent.right) {leftRotate(parent); //从父节点处左旋RBNode<T> tmp = parent; //然后将父节点和自己调换一下,为下面右旋做准备parent = node;node = tmp;}//case3: 叔叔节点是黑色,且当前节点是左子节点setBlack(parent);setRed(gparent);rightRotate(gparent);} else { //若父节点是祖父节点的右子节点,与上面的完全相反,本质一样的RBNode<T> uncle = gparent.left;//case1: 叔叔节点也是红色if(uncle != null & isRed(uncle)) {setBlack(parent);setBlack(uncle);setRed(gparent);node = gparent;continue;}//case2: 叔叔节点是黑色的,且当前节点是左子节点if(node == parent.left) {rightRotate(parent);RBNode<T> tmp = parent;parent = node;node = tmp;}//case3: 叔叔节点是黑色的,且当前节点是右子节点setBlack(parent);setRed(gparent);leftRotate(gparent);}}//将根节点设置为黑色setBlack(this.root);}/*********************** 删除红黑树中的节点 **********************/public void remove(T key) {RBNode<T> node;if((node = search(root, key)) != null)remove(node);}private void remove(RBNode<T> node) {RBNode<T> child, parent;boolean color;//1. 被删除的节点“左右子节点都不为空”的情况if((node.left != null) && (node.right != null)) {//先找到被删除节点的后继节点,用它来取代被删除节点的位置RBNode<T> replace = node;//  1). 获取后继节点replace = replace.right;while(replace.left != null) replace = replace.left;//  2). 处理“后继节点”和“被删除节点的父节点”之间的关系if(parentOf(node) != null) { //要删除的节点不是根节点if(node == parentOf(node).left) parentOf(node).left = replace;elseparentOf(node).right = replace;} else { //否则this.root = replace;}//  3). 处理“后继节点的子节点”和“被删除节点的子节点”之间的关系child = replace.right; //后继节点肯定不存在左子节点!parent = parentOf(replace);color = colorOf(replace);//保存后继节点的颜色if(parent == node) { //后继节点是被删除节点的子节点parent = replace;} else { //否则if(child != null) setParent(child, parent);parent.left = child;replace.right = node.right;setParent(node.right, replace);}replace.parent = node.parent;replace.color = node.color; //保持原来位置的颜色replace.left = node.left;node.left.parent = replace;if(color == BLACK) { //4. 如果移走的后继节点颜色是黑色,重新修整红黑树removeFixUp(child, parent);//将后继节点的child和parent传进去}node = null;return;}}//node表示待修正的节点,即后继节点的子节点(因为后继节点被挪到删除节点的位置去了)private void removeFixUp(RBNode<T> node, RBNode<T> parent) {RBNode<T> other;while((node == null || isBlack(node)) && (node != this.root)) {if(parent.left == node) { //node是左子节点,下面else与这里的刚好相反other = parent.right; //node的兄弟节点if(isRed(other)) { //case1: node的兄弟节点other是红色的setBlack(other);setRed(parent);leftRotate(parent);other = parent.right;}//case2: node的兄弟节点other是黑色的,且other的两个子节点也都是黑色的if((other.left == null || isBlack(other.left)) && (other.right == null || isBlack(other.right))) {setRed(other);node = parent;parent = parentOf(node);} else {//case3: node的兄弟节点other是黑色的,且other的左子节点是红色,右子节点是黑色if(other.right == null || isBlack(other.right)) {setBlack(other.left);setRed(other);rightRotate(other);other = parent.right;}//case4: node的兄弟节点other是黑色的,且other的右子节点是红色,左子节点任意颜色setColor(other, colorOf(parent));setBlack(parent);setBlack(other.right);leftRotate(parent);node = this.root;break;}} else { //与上面的对称other = parent.left;if (isRed(other)) {// Case 1: node的兄弟other是红色的  setBlack(other);setRed(parent);rightRotate(parent);other = parent.left;}if ((other.left==null || isBlack(other.left)) &&(other.right==null || isBlack(other.right))) {// Case 2: node的兄弟other是黑色,且other的俩个子节点都是黑色的  setRed(other);node = parent;parent = parentOf(node);} else {if (other.left==null || isBlack(other.left)) {// Case 3: node的兄弟other是黑色的,并且other的左子节点是红色,右子节点为黑色。  setBlack(other.right);setRed(other);leftRotate(other);other = parent.left;}// Case 4: node的兄弟other是黑色的;并且other的左子节点是红色的,右子节点任意颜色setColor(other, colorOf(parent));setBlack(parent);setBlack(other.left);rightRotate(parent);node = this.root;break;}}}if (node!=null)setBlack(node);}/****************** 销毁红黑树 *********************/public void clear() {destroy(root);root = null;}private void destroy(RBNode<T> tree) {if(tree == null) return;if(tree.left != null) destroy(tree.left);if(tree.right != null) destroy(tree.right);tree = null;}/******************* 打印红黑树 *********************/public void print() {if(root != null) {print(root, root.key, 0);}}/** key---节点的键值* direction--- 0:表示该节点是根节点*              1:表示该节点是它的父节点的左子节点*              2:表示该节点是它的父节点的右子节点*/private void print(RBNode<T> tree, T key, int direction) {if(tree != null) {if(0 == direction) System.out.printf("%2d(B) is root\n", tree.key);elseSystem.out.printf("%2d(%s) is %2d's %6s child\n", tree.key, isRed(tree)?"R":"b", key, direction == 1?"right":"left");print(tree.left, tree.key, -1);print(tree.right, tree.key, 1);}}
}

       下面附上测试程序吧:

 

 

package test;import tree.RBTree;public class RBTreeTest {private static final int a[] = {10, 40, 30, 60, 90, 70, 20, 50, 80};private static final boolean mDebugInsert = true;    // "插入"动作的检测开关(false,关闭;true,打开)private static final boolean mDebugDelete = true;    // "删除"动作的检测开关(false,关闭;true,打开)public static void main(String[] args) {int i, ilen = a.length;RBTree<Integer> tree = new RBTree<Integer>();System.out.printf("== 原始数据: ");for(i=0; i<ilen; i++)System.out.printf("%d ", a[i]);System.out.printf("\n");for(i=0; i<ilen; i++) {tree.insert(a[i]);// 设置mDebugInsert=true,测试"添加函数"if (mDebugInsert) {System.out.printf("== 添加节点: %d\n", a[i]);System.out.printf("== 树的详细信息: \n");tree.print();System.out.printf("\n");}}System.out.printf("== 前序遍历: ");tree.preOrder();System.out.printf("\n== 中序遍历: ");tree.inOrder();System.out.printf("\n== 后序遍历: ");tree.postOrder();System.out.printf("\n");System.out.printf("== 最小值: %s\n", tree.minValue());System.out.printf("== 最大值: %s\n", tree.maxValue());System.out.printf("== 树的详细信息: \n");tree.print();System.out.printf("\n");// 设置mDebugDelete=true,测试"删除函数"if (mDebugDelete) {for(i=0; i<ilen; i++){tree.remove(a[i]);System.out.printf("== 删除节点: %d\n", a[i]);System.out.printf("== 树的详细信息: \n");tree.print();System.out.printf("\n");}}}}

 

5.红-黑树的复杂度

        前面也说了,当数据以升序或降序插入时,二叉搜索树的性能就会下降到最低,但是红-黑树的自我修复功能保证了即使在最坏的情况下,也能保证时间复杂度在O(logN)的级别上。

        至此,红-黑树的所有内容基本上讨论完了,如有错误之处,欢迎留言指正~

 

        【正在看本人博客的这位童鞋,我看你气度不凡,谈吐间隐隐有王者之气,日后必有一番作为!下面有个“顶”字,你就顺手把它点了吧~相的准,我分文不收;相不准,你也好回来找我~嘎嘎嘎】

 

 

_____________________________________________________________________________________________________________________________________________________

-----乐于分享,共同进步!

-----本文动态图出自:http://www.cnblogs.com/yangecnu/p/Introduce-Red-Black-Tree.html

-----本文部分参考于博客专家July的这篇文章:http://blog.csdn.net/v_july_v/article/details/6105630

-----更多文章请看:http://blog.csdn.net/eson_15

转载于:https://my.oschina.net/u/3425573/blog/3008174

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/537007.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构与算法——二叉树、堆、优先队列

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 七、树 7.1 树 7.1.1 树的定义 树是我们计算机中非常重要的一种数据结构&#xff0c;同时使用树这种数据结构&#xff0c;可以描述现实生活…

继牛津大学后,加大伯克利分校等多家美国高校终止与华为合作

文&#xff0f;AI财经社 唐煜编&#xff0f;嵇国华据 Nature News 报道&#xff0c;在美国相关部门的压力之下&#xff0c;加州大学伯克利分校&#xff08;UC Berkeley&#xff09;近日宣布不再与华为签署新的研究合作&#xff1b;德州大学奥斯丁分校也正在审查自身与华为的关系…

为什么varchar字段长度最好是2的n次方-1

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 计算机是二进制计算的&#xff0c;1 bytes 8 bit ,一个字节最多可以代表的数据长度是2的8次方 11111111 在计算机中也就是-128到127。 而var…

Python之celery的简介与使用

celery的简介 celery是一个基于分布式消息传输的异步任务队列&#xff0c;它专注于实时处理&#xff0c;同时也支持任务调度。它的执行单元为任务&#xff08;task&#xff09;&#xff0c;利用多线程&#xff0c;如Eventlet&#xff0c;gevent等&#xff0c;它们能被并发地执行…

不使用比较运算符如何比较两个数的大小

分享一波:程序员赚外快-必看的巅峰干货 前言 今天在水群的过程中看到有位群员谈论到这个话题&#xff0c;是他找工作过程中某家公司的面试题&#xff08;到底是哪家公司才会出这种没营养的题目刁难别人&#xff09;&#xff0c;有点兴趣&#xff0c;就开始写了。 开搞 想了一…

java占位符填充_Java使用freemark生成word

1、制作模板先用office word做一个模板word文档&#xff0c;${usrName}、${nowDate}占位符 可以使用 office 或者 wps 先创建一个模板表格 &#xff08;替换$部分可以在 模板格式改变之后 在替换xml 格式改了后有些原本的字符会分开&#xff09;2、用office word将模板word另存…

Java中如何使用非阻塞异步编程——CompletableFuture

分享一波:程序员赚外快-必看的巅峰干货 对于Node开发者来说&#xff0c;非阻塞异步编程是他们引以为傲的地方。而在JDK8中&#xff0c;也引入了非阻塞异步编程的概念。所谓非阻塞异步编程&#xff0c;就是一种不需要等待返回结果的多线程的回调方法的封装。使用非阻塞异步编程…

城市运行一网统管_【宣传活动】持续开展城市运行“一网统管”建设宣传活动...

为进一步推进本镇城市运行“一网统管”建设工作&#xff0c;提高城市治理能力和治理水平&#xff0c;提升社会各界的知晓度和参与度&#xff0c;激发职能部门人员、党员、群众参与“一网统管”工作的热情。9月10日&#xff0c;镇网格中心于福泉居委会议室开展“推进城市运行‘一…

Java如何只使用位运算实现加减乘除

分享一波:程序员赚外快-必看的巅峰干货 前言 接前面一篇博客&#xff0c;这又是某个公司的奇葩面试题&#xff08;都说了到底是哪家公司才会出这种没营养的面试题&#xff09;。不过吐槽归吐槽&#xff0c;这个题目还是有点学问的&#xff0c;比前面那个 不使用比较运算符如何…

pmc订单表格_复工了,读一则“如何提升订单准交率和生产效率”的真实故事

故事发生在中国南方小镇上一个做办公家具的公司……家具公司创建于1995年&#xff0c;是一家集研发、生产、销售、服务为一体的现代办公家具、酒店家具制造企业。主要产品有实木班台系列、会议台系列、职员桌系列、屏风系列、沙发系列、办公座椅、酒店家具系列。在省外还有两个…

GET和POST请求到底有什么区别?

分享一波:程序员赚外快-必看的巅峰干货 看到这个标题&#xff0c;想必大部分人都已经想关掉这篇博客了。先别急&#xff0c;你真的知道这两个的区别吗&#xff1f; 做过WEB开发的朋友可能很熟悉&#xff0c;看到这个问题能立马脱口而出二者的区别。 GET在浏览器回退时是无害的…

有赞电商云应用框架设计

背景 有赞是 SaaS 公司&#xff0c;向商家提供了全方位的软件服务&#xff0c;支撑商家进行采购、店铺、商品、营销、订单、物流等等管理服务。 在这个软件服务里&#xff0c;能够满足大部分的商家&#xff0c;为商家保驾护航。 但是很多大商家往往会有自己的特殊需求&#xff…

vivado 如何创建工程模式_基于Vivado的FPGA高性能开发研修班2019年8月30日上海举行...

一、课程介绍&#xff1a;从7系列FPGA开始&#xff0c;Xilinx提出了Vivado Design Suite设计软件&#xff0c;提供全新构建的SoC 增强型、以 IP 和系统为中心的下一代开发环境&#xff0c;以解决系统级集成和实现的生产力瓶颈。同时&#xff0c;Xilinx专门针对Vivado推出了Ultr…

程序员的自我修养——远离“外包思维”

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 在我们做开发的日子里&#xff0c;不免会进行跳槽&#xff0c;跳来跳去公司无非就分成两大类——互联网公司、外包公司。当然我们本次讨论的并…

英特尔为 Kubernetes 推出分布式深度学习平台:Nauta

2019独角兽企业重金招聘Python工程师标准>>> 随着人工智能的发展&#xff0c;深度学习的价值不断增长&#xff0c;但实现它可能是一个复杂耗时的过程。英特尔(Intel)正寻求通过其在 Kubernetes 进行分布式深度学习的新开源平台来改变这一状况&#xff0c;该深度学习…

pytorch梯度下降函数_Pytorch中常用的四种优化器SGD、Momentum、RMSProp、Adam

来源&#xff1a;AINLPer微信公众号编辑: ShuYini校稿: ShuYini时间: 2019-8-16 引言很多人在使用pytorch的时候都会遇到优化器选择的问题&#xff0c;今天就给大家介绍对比一下pytorch中常用的四种优化器。SGD、Momentum、RMSProp、Adam。随机梯度下降法&#xff08;SGD&#…

python计算无穷级数求和常用公式_傅里叶变换(二) 从傅里叶级数到傅里叶变换...

在上一部分当中&#xff0c;得到了利用三角函数表示周期函数的方法&#xff0c;但是对于非周期函数就...凉了。所以有什么办法吗&#xff1f;没办法&#xff08;划掉&#xff09;。这时候我们就需要拿出来我们的黑科技——傅里叶变换。一、傅里叶级数的推广当然这东西肯定不是凭…

中鸣投篮机器人怎么组装_1000余人参加洛阳市青少年机器人竞赛

机器人智能识别地面上的黑色线条&#xff0c;并沿着线条来到指定位置&#xff0c;放下“快递包裹”&#xff1b;无人机在空中飞舞&#xff0c;时而钻过圆环&#xff0c;时而来个空翻&#xff0c;犹如跳芭蕾般在空中划过一道优美曲线&#xff1b;橘红色乒乓球从筒道中送出&#…

Exchange队列优先级介绍和配置

一、场景 在日常办公环境中所有邮件都会存在重要与非重要的情况&#xff0c;并且不同的邮箱的使用人的级别也不一样&#xff0c;不一样的职位级别要求不一样的运维等级&#xff0c;以及发送邮件要求的速度也不一样。这就导致了邮件需要按照重要性进行分类&#xff0c;重要的邮件…

Mybatis源码阅读(一):Mybatis初始化1.3 —— 解析sql片段和sql节点

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…