首页 > 技术文章 > 红黑树(Reb-Black-Tree)

codeMedita 2021-12-07 23:15 原文

红黑树(Red-Black-Tree)也是一个二叉搜索树,和平衡二叉搜索树(AVL-Tree)一样,它也是通过约定某些特性来对节点进行旋转操作,从而保证树的平衡,而不像二叉查找树那样,在极端情况下树的结构严重失衡,导致在查找节点的时候效果比较差;现根据《算法导论》对红黑树的性值进行分析,红黑树有以下特性:

1、每个节点或是红的、或是黑的,也即非黑即红。

2、根节点是黑色的。

3、每个叶子节点(NIL)是黑色的。

4、如果一个节点是红色的,则它的两个子节点都是黑色的。

5、对于每个节点,从该节点到其所有后待叶子节点的简单路径上,均含有相同个数的黑色节点。

以《算法导论》中的示例为例子,下面是其给的一个示例:

 这个示例中叶子节点的两个左右空的子节点也被视为黑节点,为了方便判断边界条件,它定义一个黑色的nil节点,所有的叶子节点的左右孩子指向该节点,根节点的父指针也指向该节点;红黑树在操作过程中能够保证最坏的条件下时间复杂度为O(lgn);但是它没有做到像AVL-Tree那样比价“完美的”平衡,例如上图中的节点41的左右孩子的高度差是2,在AVL树中已经达到了旋转的条件,会对其进行旋转操作。红黑树只涉及到左旋和右旋,定义如下:

上图为左右旋转的一个过程,涉及到子节点的变动,节点的左右旋转的操作如下:

1、左旋操作

def left_rotate(x):

  y=x.right;

  x.right=y.left;

  if y.left != nil:

    y.left.parent=x;

  y.parent=x.parent;

  //修改x的父节点的指向

  if x.parent==nil://说明x是根节点

    root=y;

  else if x.parent.left==x:

    x.parent.left=y;

  else

    x.parent.right=y;

  y.left=x;  //x成为y的左孩子

  x.parent=y;  //修改x节点的父指针

2、右旋操作

def Right_Rotate(x):

  y=x.left;

  x.left=y.right;

  if y.right!=nil:

   y.right.parent=x;

  if x.parent == nil:

    root=y;

  else if x.parent.left==x: 

    x.parent.left=y;

  else 

    x.parent.right=y;

  y.right=x;

  x.parent=y;

以上为红黑树左右旋转的基本操作,插入操作过程如下:

def rb_insert(z): //这里z是一个节点

  y=nil  //记录nil节点

  x=root //记录根节点

  //寻找父节点

  while x != nil:

    y=x;

    if z.val < x.val:

      x=x.left

    else x=x.right

  z.parent=y;

  if y==nil: //说明树是空的

    root=z

  else if z.val < y.val:

    y.left=z

  else y.right=z

  z.left=nil;

  z.right=nil;

  z.color=RED //将新插入的节点设置为红色

  rb_insert_fixup(z);

上面的插入操作有一个rb_insert_fixup函数,具体实现如下:

def rb_insert_fixup(z):

  //因为z是新插入的接点,是红的,如果其父节点也是红的,则必然是冲突的

  while z.p.color == RED

    //***********旋转情况1***********

    if z.parent==z.parent.parent.left: // z的父节点是左孩子

      y=z.parent.parent.right;//右叔父节点

      // *********情况 1*********

      if y.color == RED://右叔父节点是红色的

        //以下代码是将父节点和叔父节点均设置为黑色节点,将祖父节点设置为红色

        z.parent.color=BLACK

        y.color=BLACK

        z.parent.parent.color=RED

        z=z.parent.parent;// 将z设置为祖父节点继续进行判断

      else // 叔父节点不是红的,以下分两种情况

        // *********情况2*********

        if z == z.parent.right: // z是右孩子节点

          z = z.parent  //将z指向其父

          left_rotate(z)

        else   // z 是左孩子

          // *********情况3*********

          z.parent.color = BLACK

          z.parent.parent.color = RED

          Right_Rotate(z.parent.parent)

    else: // z的父节点是右孩子

      y=z.parent.parent.left

      if y.color == RED:

        z.parent.color=BLACK

        y.color=BLACK

        z.parent.parent.color=RED

        z=z.parent.parent

      else: //叔父节点不是红节点,分两种情况

        if z==z.parent.left:

          z=z.parent

          right_rotate(z)

        else:

          z.parent.color=BLACK

          z.parent.parent.color=RED

          left_rotate(z.parent.parent)

  root.color=BLACK;

上面的旋转分为三种情况,因为上面的条件具有对称性,以旋转情况1为例,也即插入节点的父节点为左孩子的情况,分析如下:

1、叔父节点为红节点的情况

 注意上面的 z 指针最后会指向祖父节点。

2、叔父节点为黑节点的情况

  如上图,插入节点B,B是节点A的右孩子,需要对A进行左旋转然后将B节点置黑,将C节点置红,向右旋转C节点;情况2需要转换成情况3,也就是上图中的第一张图经过旋转调整到第二张图。说明,如果A是红的,则说明新插入的节点B的祖父节点C一定是黑的。以上为插入操作,现定义删除功能。删除功能比较复杂,如果删除的节点的颜色是红的,则其所在子树的黑高是不变的;如果是黑色的,则其子树的黑高会减一,势必造成其父节点的左右子树黑高失衡。具体操作如下:

def rb_transplant(u,v):

   if u==nil:

         root=v

   else if u.parent.left == u:

    u.parent.left=v

   else

        u.parent.right=v

   v.parent=u.parent

上面这个方法用于将u节点的父节点的指针指向v节点,是用于调整u节点父节点的指针指向功能。

def get_successor(node):

 if node.right != nil: //哨兵节点

  rightNode = node.right

  while rightNode.left!=nil:

    rightNode=rightNode.left

  return rightNode

   p=node.parent;ch=node;

 while p != nil && ch == p.right:

  ch=p;

  p=p.parent;

  return p;

上面这个方法用于寻找后继节点,一个节点的后继节点就是中序遍历结果中第一个比自己大的那个节点

关键的时刻到了,删除节点定义如下:

def rb_delete(node): // 删除节点node,这个node是根据val找到的

    if node.left != nil && node.right != nil:

     p = get_successor(node) // 找到继承节点,将继承节点替代自己,然后删除这个继承节点

  node.val = p.val;

  node = p;

    replacement = node.left != nil ? node.left : node.right //找到需要替代自己的节点

 // 后继节点替代了待删除的节点,后继节点也需要一个节点来补充自己的位置,

   //如果有相关节点来补充自己的位置,则执行以下操作

 if replacement != nil:

  //以下操作是将自己的父节点指向自己的后继节点

  if node.parent == nil:

    root = replacement

  if node.parent.left == node:

    node.parent.left = replacement

  else

    node.parent.right = replacement

  node.parent = node.right = node.left = null;  // 删除自己

  if node.color == black:

    delete_fixup(replacement) ; //调整节点黑高

   else if node.parent == nil: //该树只有一个节点,并且根节点就是待删除的节点

  root = null;

  else:// 没有相关节点来补充后继节点的位置,如果后继节点是黑色的,则进行所在子树的黑高调整,调整完毕后再将后继节点删除

    // 如果后继节点不是黑色的,则直接进行删除操作

  if node.color == black:

    delete_fixup(node);

  if node.parent.left == node:

    node.parent.left = nil;

  else: node.parent.right = nil;

  node.parent = node.left = node.right = null;

上面这个函数是用于节点的删除操作,里面有一个关键性步骤,delete_fixup函数,用于调整黑高的,定义如下:

def delete_fixup(x):

  while x != root && x.color == black:

    if x == x.parent.left:

      w = x.parent.right; // 兄弟节点

      //情况1,需要将情况1转成情况2

      if w.color == red:

        w.color = black;   //将兄弟节点设置为黑

        x.parent.color = red; // 将父节点设置为红

        leftRotate(x.parent); //对父节点进行左旋

        w=x.parent.right;

      //情况2

      if w.left.color == black and w.right.color == black:

        w.color = red;//如果兄弟节点的左右孩子都是黑色的,将自己的颜色设置为红色

        x = x.parent;//这样兄弟节点的黑高和自己一样了,在将指针指向父节点继续进行相同的判断

      //情况3 需要将情况3转换成情况4

      else if w.right.color == red; // 兄弟节点的右孩子是红色的

        w.right.color = black;//将右孩子设置为黑

        w.color = red; //将兄弟节点设置为红

        rightRotate(w);

        x=x.parent.right;

      //情况4,恢复黑高的关键性一步

      w.color = x.parent.color

      x.parent.color = black; // x 的父节点设置为黑

      w.right.color = black;

      leftRotate(x.parent); // 进行左旋操作,这样x所在子树的黑高就增加了1,本来x所在子树的黑高因删除操作是减一的,现在变平衡了

      x = root;

    else: 与上面的结构是对称的

  x.color = black;

 

 

 

 

 

以上是情况1变成情况2,对情况2继续进行相同的判断操作;情况3经过调整变成情况4,情况4是真正的进行黑高调整的关键性一步。

完整的代码示例如下:

 

package rbtree;

import java.util.ArrayList;
import java.util.List;

public class RBTree {

    public static void main(String[] args) {
        RBTree tree = new RBTree();
        tree.insert(1).insert(2).insert(3).insert(4).insert(5).insert(6);
        tree.printByLevel();
        tree.deleteVal(6);
        tree.printByLevel();
        tree.deleteVal(4);
        tree.printByLevel();
    }
    
    //nil是哨兵节点
    private Node root, nil = new Node();

    /**
     * 插入操作
     * @param val
     * @return
     */
    public RBTree insert(int val) {
        Node node = new Node(val);
        if (root == null) {
            root = node;
            root.parent = nil;
            root.left = nil;
            root.right = nil;
        } else {
            //寻找父节点
            Node x = root, y = root;
            while (x != nil) {
                y = x;
                if (val < x.val) {
                    x = x.left;
                } else {
                    x = x.right;
                }
            }
            node.parent = y;
            if (val < y.val) {
                y.left = node;
            } else {
                y.right = node;
            }
            node.left = nil;
            node.right = nil;
            //插入的节点设置为红色
            node.color = Color.red;
            fixUp(node);
        }
        return this;
    }

    /**
     * 删除某个值,先遍历树,找到相应的节点进行删除
     *
     * @param val
     */
    public void deleteVal(int val) {
        Node targetNode = null, temp = root;
        while (temp != null) {
            if (val < temp.val) {
                temp = temp.left;
            } else if (val > temp.val) {
                temp = temp.right;
            } else {
                //寻找到目标节点
                targetNode = temp;
                break;
            }
        }
        //没有找到目标节点,直接返回操作
        if (targetNode == null) {
            return;
        }
        //将目标节点从根节点删除
        deleteNode(targetNode);
    }

    /**
     * 将节点从树中删除,删除判断:
     * Color.red、如果node是叶子节点,则直接删除
     * 2、如果node有子树,寻找node的后继节点,将其替代自己, 同时将后继节点删除
     * 以上过程,无论是node节点本身还是node的后继节点被删除,都需要判断删除节点的颜色,如果是黑色,则黑高一定被破坏,需要进行调整
     *
     * @param node
     */
    private void deleteNode(Node node) {

        if (node.left != nil && node.right != nil) {
            //找到后继节点
            Node p = getSuccessor(node);
            //将后继节点的值替换自己的值
            node.val = p.val;
            //将待删除的指针指向后继节点
            node = p;
        }
        //node指向的是待删除的节点,replacement指向替代自己的后继节点
        Node replacement = (node.left != nil ? node.left : node.right);
        if (replacement != nil) {
            replacement.parent = node.parent;
            if (node.parent == nil) {
                root = replacement;
            } else if (node.parent.left == node) {
                node.parent.left = replacement;
            } else {
                node.parent.right = replacement;
            }
            //删除node节点,使得没有指针指向该节点
            node.parent = null;
            node.right = null;
            node.left = null;
            //如果要删除的节点的颜色是黑色的,则需要进行调整
            if (node.color == Color.black) {
                //删除调整
                deleteFixUp(replacement);
            }
        } else if (node.parent == nil) {//该树只有一个节点
            root = null;
        } else {
            //node节点没有replacement节点,说明其本身是叶子节点,这时候将node节点本身设置为一个replacement节点,
            // 然后对其进行调整,调整完毕后,再将其删除即可
            if (node.color == Color.black) {
                deleteFixUp(node);
            }
            //直接将node节点删除
            if (node.parent.left == node) {
                node.parent.left = nil;
            } else {
                node.parent.right = nil;
            }
            node.parent = null;
            node.left = null;
            node.right = null;
        }
    }

    /**
     * 寻找后继节点,中序遍历过程中第一个比自己大的那个节点为后继节点
     * @param node
     * @return
     */
    private Node getSuccessor(Node node) {
        if (node.right != nil) {
            Node result = node.right;
            while (result.left != nil) {
                result = result.left;
            }
            return result;
        }
        Node p=node.parent;
        Node ch=node;
        while(p!=nil && ch==p.right){
            ch=p;
            p=p.parent;
        }
        return p;
    }

    /**
     * 删除操作的调整,x是删除节点的后继节点,如果删除节点是黑色的,后继节点也是黑色的,
     * 则删除节点的黑高一定比其兄弟节点的黑高小1,需要通过调整,使其黑高和兄弟节点的黑高保持一致
     * 这里关键性的黑高调整在情况2、4;如果是情况1和3的话,需要分别将其转成情况2和4
     * 情况2是一个迭代判断,将x的兄弟节点的黑高减一,和自己的黑高保持一致,然后将x指向其父节点继续进行相同的判断
     * 情况4则是黑高的调整了,通过旋转,将自己所在子树的黑高增加1,这样就和兄弟节点所在子树的黑高保持了一致
     *
     * @param x
     */
    private void deleteFixUp(Node x) {
        //如果x节点是红色的,则直接将其设置为黑色即可,这样x节点的父节点的黑高保持不变,
        // 如果x是黑色的,则x父节点的黑高是不稳定的,需要进行调整
        while (x != root && x.color == Color.black) {
            if (x == x.parent.left) {
                //兄弟节点
                Node w = x.parent.right;
                //*********情况1**********
                //将情况1转成情况2
                if (w.color == Color.red) {
                    w.color = Color.black;
                    x.parent.color = Color.red;
                    leftRotate(x.parent);
                    w = x.parent.right;
                }
                //注意不能是哨兵节点
                if (w != nil) {
                    //*********情况2**********
                    //如果兄弟节点w(w本身是黑色的,如果是红色的,经过情况1的调整,w指向的节点最终会是黑色的)的左右孩子都是黑色的
                    if (w.left.color == Color.black &&
                            w.right.color == Color.black) {
                        //直接将w设置为红色,这样x的父节点的到叶子节点的黑高是平衡的,因为x兄弟节点的黑高减一,变得与自己一样了
                        // 再将x指向父节点,继续进行黑高的判断
                        w.color = Color.red;
                        x = x.parent;
                    } else {
                        if (w.right.color == Color.black) {
                            //*********情况3**********
                            //该情况下需要进行旋转操作,将其变成情况四
                            w.left.color = Color.black;
                            w.color = Color.red;
                            rightRotate(w);
                            w = x.parent.right;
                        }
                        //*********情况4**********
                        //注意,这里是恢复黑高的关键一步
                        //将x父节点设置为黑色,同时对x的父节点进行左旋操作,x的父节点进行左旋的话,x的兄弟节点w会替代x父节点,为了维持
                        //黑高不变,需要将w的颜色设置为x父节点颜色,x父节点变成了w的左子树,这样黑高又变得平衡了
                        w.color = x.parent.color;
                        x.parent.color = Color.black;
                        w.right.color = Color.black;
                        leftRotate(x.parent);
                        x = root;
                    }
                }

            } else {
                //与上面是对称的
                Node w = x.parent.left;
                //*********情况1**********
                //将情况1转成情况2
                if (w.color == Color.red) {
                    w.color = Color.black;
                    x.parent.color = Color.red;
                    rightRotate(x.parent);
                    w = x.parent.left;
                }
                //注意不能是哨兵节点
                if (w != nil) {
                    //*********情况2**********
                    if (w.left.color == Color.black &&
                            w.right.color == Color.black) {
                        w.color = Color.red;
                        x = x.parent;
                    } else {
                        if (w.left.color == Color.black) {
                            //*********情况3**********
                            //将该情况转成情况4
                            w.right.color = Color.black;
                            w.color = Color.red;
                            leftRotate(w);
                            w = x.parent.left;
                        }
                        //*********情况4**********
                        //恢复黑高的关键性一步
                        w.color = x.parent.color;
                        x.parent.color = Color.black;
                        w.left.color = Color.black;
                        rightRotate(x.parent);
                        x = root;
                    }
                }

            }
        }
        x.color = Color.black;
    }

    //将节点u的父节点指向v
    private void rbTransplant(Node u, Node v) {
        if (u.parent == nil) {
            root = v;
        } else if (u.parent.left == u) {
            u.parent.left = v;
        } else {
            u.parent.right = v;
        }
        v.parent = u.parent;
    }

    /**
     * 插入操作调整节点
     *
     * @param node
     */
    private void fixUp(Node node) {
        //父节点是红节点
        while (node.parent.color == Color.red) {
            //父节点是左孩子
            if (node.parent == node.parent.parent.left) {
                Node uncle = node.parent.parent.right;
                //叔父节点是红节点
                if (uncle.color == Color.red) {
                    node.parent.color = Color.black;
                    uncle.color = Color.black;
                    node.parent.parent.color = Color.red;
                    node = node.parent.parent;
                    //以下是叔父节点是黑节点的情况,需要涉及到旋转
                } else if (node == node.parent.right) {
                    node = node.parent;
                    leftRotate(node);
                } else {
                    node.parent.color = Color.black;
                    node.parent.parent.color = Color.red;
                    rightRotate(node.parent.parent);
                }

            } else {
                Node uncle = node.parent.parent.left;
                if (uncle.color == Color.red) {
                    node.parent.color = Color.black;
                    uncle.color = Color.black;
                    node.parent.parent.color = Color.red;
                    node = node.parent.parent;
                } else if (node == node.parent.left) {
                    node = node.parent;
                    rightRotate(node);
                } else {
                    node.parent.color = Color.black;
                    node.parent.parent.color = Color.red;
                    leftRotate(node.parent.parent);
                }

            }
        }
        root.color = Color.black;
    }

    /**
     * 向右旋转
     *
     * @param node
     */
    private void rightRotate(Node node) {
        Node left = node.left;
        Node leftRight = left.right;
        node.right = leftRight;
        if (leftRight != nil) {
            leftRight.parent = node;
        }
        left.parent = node.parent;
        if (node.parent == nil) {
            root = left;
        } else if (node.parent.left == node) {
            node.parent.left = left;
        } else {
            node.parent.right = left;
        }
        left.right = node;
        node.parent = left;
    }

    /**
     * 向左旋转
     *
     * @param node
     */
    private void leftRotate(Node node) {
        Node right = node.right;
        Node rightLeft = right.left;
        node.right = rightLeft;
        if (rightLeft != nil) {
            rightLeft.parent = node;
        }
        right.parent = node.parent;
        if (node.parent == nil) {
            root = right;
        } else if (node.parent.left == node) {
            node.parent.left = right;
        } else {
            node.parent.right = right;
        }
        right.left = node;
        node.parent = right;
    }

    public void printByLevel() {
        System.out.println("=========================");
        List<Node> temp = new ArrayList<>();
        if (root != null) {
            temp.add(root);
        }
        while (temp.size() > 0) {
            List<Node> nodes = new ArrayList<>();
            for (Node obj : temp) {
                System.out.print(obj);
                if (obj.left != nil) {
                    nodes.add(obj.left);
                }
                if (obj.right != nil) {
                    nodes.add(obj.right);
                }
            }
            System.out.println();
            temp.clear();
            temp.addAll(nodes);
        }
    }
}

class Node {
    public int val;
    public Node left, right, parent;
    //Color.black 表示黑色 Color.red表示红色
    public Color color = Color.black;

    public Node(int val) {
        this.val = val;
    }

    public Node() {

    }

    @Override
    public String toString() {
        return "{ value=" + val + ",color=" + color + "} ";
    }
}

enum Color {
    black,
    red;
}
View Code

 

红黑树的插入、删除、查询操作可保证效率为O(lgn),但是整体平衡性没有AVL树好,它也有自己的有点:插入删除的时候节点调整比较少,不像AVL

树调整那么频繁;所以如果插入、删除操作频繁的操作可采用红黑树结构,如果查询操作频繁的话可采用AVL树。 

推荐阅读