数据结构(六):复杂树之红黑树

 

一、 红黑树概述

  红黑树是对2-3树的编码,即用二叉树结点单键单值的形式来表示2-3树,具体措施是对两个结点相连的链接标记颜色。

  红链接:将相连的两个2-结点链接起来表示一个3-结点,即我们将3-结点表示为由一条左斜的红色链接相连的两个2-结点

  黑链接:即普通的2-结点

二、 红黑树特性

  红黑树是存在红黑链接并且满足如下特性的二叉树:

  1、 红链接都为左链接

  2、 没有任何一个结点同时和两条红色链接相连

  3、 红黑树是完美平衡的二叉树,即任意空链接到根结点的黑链接数量相等

  4、 红黑树的根结点链接永远是黑色

  如下为2-3树表示为红黑树的示意图

  红黑树:

   

  2-3树:

   

三、 红黑树结点的构造

  我们在二叉树结点的结构上,新增一个布尔变量color表示结点与父节点相连的链接的颜色,红链接为true,黑链接为false

/**

 * 红黑树结点

 * @author jiyukai

 */

public class Node<Key,Value> {

 

       //结点键

       public Key key;

      

       //结点值

       public Value value;

      

       //结点左子树

       public Node left;

      

       //结点右子树

       public Node right;

      

       //结点指向父节点的链接颜色,red为true,black为false

       public boolean color;

 

       public Node(Key key, Value value, Node left, Node right, boolean color) {

              super();

              this.key = key;

              this.value = value;

              this.left = left;

              this.right = right;

              this.color = color;

       }

      

}

      

四、 红黑树平衡化方法

  在对红黑树进行增删改查操作后,很有可能会出现

  1、红链接为右链接

  2、一个结点同时和两条红色链接相连

  这些都是和红黑树的特性不匹配的存在,因此我们需要做一些平衡化的操作,来保证红黑树平衡的同时,又满足既有的特性:

  4.1左旋

  当某个节点的左链接为黑色,右链接为红色,此时需要左旋,左旋的步骤和示意图如下:

   

  1、让当前结点H的右子结点X的左子结点,变为当前结点H的右子结点

  2、让当前结点H成为X的左子结点

  3、让H的color变成X的color

  4、让H的color变为红色

   

  4.2右旋

  当某个节点的左子结点链接是红色,并且左子节点的左子节点链接也是红色,此时需要右旋,右旋的步骤和示意图如下:

   

  1、让H的右子结点成为X的左子节点

  2、让X成为H的右子结点

  3、让H的color变为X的color

  4、让H的color变为red

   

  右旋后发现,当前结点变成了H,此时H的左右子结点的链接都是红色,这是不符合红黑树的特性的,那么需要如何处理呢,见如下的颜色反转方法:

  4.3颜色反转

  当一个结点的左右子结点的链接都是红色,此时只需要把两个红色链接变为黑色,同时使当前结点的链接改为红色即可(根结点除外)。

   

  若当前结点非根结点,则反转后的链接为红色

   

  若当前结点为根结点,则反转后的链接为黑色

   

五、 红黑树平衡化场景

  5.1、向单个2-结点中插入元素

  5.1.1若插入的元素比2-结点小,则新增一个红色链接的结点即可

   

  5.1.2若插入的元素比2-结点大,,则需要左旋处理

   

  左旋后

   

  5.2、向非根结点的2-结点插入新键

  往红黑树中插入C

   

  右链接为红色链接,需要左旋

   

  5.3、向单个3-结点插入新键

  5.3.1 往该3-结点中插入新键,若新键比3-结点中的键都要大

   

   

  出现B的左右链接均为红色,需要做颜色反转

   

  5.3.2往该3-结点中插入新键,若新键比3-结点中的键都要小

   

 

   

  出现连续两个左子结点为红链接的情况,需要右旋

   

  出现左右两个子结点的链接为红链接的情况,需要做颜色反转

   

  5.3.2往该3-结点中插入新键,若新键介于3-结点两个键之间

   

 

   

  出现右链接为红色链接的情况,需要左旋

   

  出现连续两个左子结点为红链接的情况,需要右旋

   

  出现左右两个子结点的链接为红链接的情况,需要做颜色反转

   

  5.4、向非根结点的3-结点中插入新键

  往红黑树中插入元素H

   

 

   

  出现连续两个左子结点为红链接的情况,需要右旋

   

  出现左右两个子结点的链接为红链接的情况,需要做颜色反转

   

  出现右链接为红色链接的情况,需要左旋

   

六、 红黑树的实现

/**

 * 红黑树

 * @author jiyukai

 * @param <Key>

 */

public class RedBlackTree<Key extends Comparable<Key>, Value> {

 

       // 根结点

       private Node root;

 

       // 元素个数

       private int N;

 

       // 红色链接布尔值

       private final static boolean RED = true;

 

       // 黑色链接布尔值

       private final static boolean BLACK = false;

 

       /**

        * 判断结点指向父节点的颜色是否为红色

        *

        * @param node

        * @return

        */

       private boolean isRed(Node x) {

              if (null == x) {

                     return false;

              }

 

              return x.color == RED;

       }

 

       /**

        * 返回红黑树的结点数量大小

        *

        * @return

        */

       private int size() {

              return N;

       }

 

       /**

        * 左旋并返回左旋后的根结点

        *

        * @return

        */

       private Node rotateLeft(Node x) {

              // 找出当前结点x的右子结点

              Node xRight = x.right;

 

              // 找出右子结点的左子结点

              Node xRightl = xRight.left;

 

              // 让当前结点x的右子结点的左子结点成为当前结点的右子结点

              x.right = xRightl;

 

              // 让当前结点x称为右子结点的左子结点

              xRight.left = x;

 

              // 让当前结点x的color变成右子结点的color

              x.color = xRight.color;

 

              // 让当前结点x的color变为RED

              x.color = RED;

 

              // 返回当前结点的右子结点

              return xRight;

       }

 

       /**

        * 右旋并返回右旋后的根结点

        *

        * @return

        */

       private Node rotateRight(Node x) {

              // 找出当前结点x的左子结点

              Node xLeft = x.left;

 

              // 找出当前结点x的左子结点的右子结点

              Node xLeftr = xLeft.right;

 

              // 让当前结点x的左子结点的右子结点称为当前结点的左子结点

              x.left = xLeftr;

 

              // 让当前结点成为左子结点的右子结点

              xLeft.right = x;

 

              // 让当前结点x的color值成为左子结点的color值

              xLeft.color = x.color;

 

              // 让当前结点x的color变为RED

              x.color = RED;

 

              // 返回当前结点的左子结点

              return xLeft;

       }

 

       /**

        * 颜色反转

        *

        * @param x

        */

       private void flipColors(Node x) {

              // 当前结点的color属性值变为RED;

              x.color = RED;

 

              // 当前结点的左右子结点的color属性值都变为黑色

              x.left.color = BLACK;

              x.right.color = BLACK;

       }

 

       /**

        * 存放键值对

        *

        * @param key

        * @param value

        */

       private void put(Key key, Value value) {

              // 根结点开始查找合适的位置存放结点

              root = put(root, key, value);

 

              // 根结点颜色改为红色

              root.color = RED;

       }

 

       /**

        * 往指定结点存放键值对,并返回存放后的结点,即新的树

        *

        * @param x

        * @param key

        * @param value

        * @return

        */

       private Node put(Node x, Key key, Value value) {

              if (x == null) {

                     // 总数+1

                     N++;

                     return new Node(key, value, null, null, RED);

              }

 

              int compare = key.compareTo((Key) x.key);

 

              if (compare > 0) {

                     // 存放的key大于当前结点,则继续遍历当前结点的右子树

                     x.right = put(x.right, key, value);

              } else if (compare < 0) {

                     // 存放的key小于当前结点,则继续遍历当前结点的左子树

                     x.left = put(x.left, key, value);

              } else {

                     // 存放的key等于当前结点,则替换

                     x.value = value;

              }

 

              // 当前结点的左链接是黑色,右链接是红色,做左旋处理

              if (isRed(x.right) && !isRed(x.left)) {

                     x = rotateLeft(x);

              }

 

              // 当前结点的左子节点h和h的左子节点都是红色链接,做右旋处理

              if (isRed(x.left) && isRed(x.left.left)) {

                     x = rotateRight(x);

              }

 

              // 当前结点的左右链接都是红色,做颜色反转处理

              if (isRed(x.left) && isRed(x.right)) {

                     flipColors(x);

              }

 

              return x;

       }

 

       /**

        * 根据Key找到Value

        *

        * @param key

        * @return

        */

       public Value get(Key key) {

              return get(root, key);

       }

 

       /**

        * 从指定结点开始找key对应的Value

        *

        * @param x

        * @param key

        * @return

        */

       public Value get(Node x, Key key) {

              if (x == null) {

                     return null;

              }

 

              int compare = key.compareTo((Key) x.key);

 

              if (compare > 0) {

                     // 查找的key大于当前结点,则继续遍历当前结点的右子树

                     return get(x.right, key);

              } else if (compare < 0) {

                     // 查找的key小于当前结点,则继续遍历当前结点的左子树

                     return get(x.left, key);

              } else {

                     return (Value) x.value;

              }

       }

}

 

posted @ 2020-11-29 15:41  纪煜楷  阅读(311)  评论(0编辑  收藏  举报