【Java】高级数据结构 --- 红黑树(上)

红黑树的介绍

红黑树(Red-Black Tree,简称R-B Tree),它一种特殊的二叉查找树。
红黑树是特殊的二叉查找树,意味着它满足二叉查找树的特征:任意一个节点的值,大于等于左孩子的值,小于等于右孩子的值。
除了具备该特性之外,红黑树还包括许多额外的信息。

红黑树顾名思义就是给节点加入了颜色,每个节点上都有存储位表示节点的颜色,颜色是红(Red)或黑(Black)。

  特点

1.红黑树不是一颗平衡树

2.红黑树节点左右子树高度差长的不能超过短的二倍                                                                                                                            红黑树牺牲了一些平衡,使得插入操作更快 (主要原因是减少了旋转的次数,但有了节点的重新着色;这就使得红黑树的增删查效率 远大于 AVL树) 

3.红黑树最多做两次旋转,删除最多旋转三次

红黑树的五个性质

 1.每个节点都有颜色,不是黑色,就是红色
 2.叶子结点(left和right)是黑色 (叶子节点,是只为空(NIL或null)的节点)
 3.根节点root必须是黑色
 4.如果一个节点是红色的,则它的子节点必须是黑色的,不能出现连续的红色节点
 5. 从根节点root到达每一个叶子结点的路径上,黑色节点的数量都是相同的  

图解

红黑树节点左右子树高度差长的不能超过短的二倍,这就意味着红黑树一颗近似平衡的二叉树,红黑树对插入时间、删除时间和查找时间
提供了最好可能的最坏情况担保,效率极大提高,因此红黑树的使用十分广泛,在计算几何中使用的很多数据结构都可以基于红黑树。

红黑树的实现(代码说明)

1.基本定义

/**
 * 枚举 红黑树节点颜色定义
 */
enum Color{
    BLACK, RED;
}

/**
 * 红黑树节点类型定义
 * @param <T>
 */
class RBNode<T extends Comparable<T>>{
    private T data; //关键字(键值)
    private RBNode<T> left; //左孩子
    private RBNode<T> right; //右孩子
    private RBNode<T> parent; //父节点
    private Color color; //颜色

    public RBNode(T data, RBNode<T> left, RBNode<T> right, RBNode<T> parent, Color color) {
        this.data = data;
        this.left = left;
        this.right = right;
        this.parent = parent;
        this.color = color;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public RBNode<T> getLeft() {
        return left;
    }

    public void setLeft(RBNode<T> left) {
        this.left = left;
    }

    public RBNode<T> getRight() {
        return right;
    }

    public void setRight(RBNode<T> right) {
        this.right = right;
    }

    public RBNode<T> getParent() {
        return parent;
    }

    public void setParent(RBNode<T> parent) {
        this.parent = parent;
    }

    public Color getColor() {
        return color;
    }

    public void setColor(Color color) {
        this.color = color;
    }
}
/**
 * 红黑树定义
 * @param <T>
 */
class RBTree<T extends Comparable<T>>{
    private RBNode<T> root;

    private Color color(RBNode<T> node){
    return node == null ? Color.BLACK : node.getColor();
    }
    
    private void setColor(RBNode<T> node, Color color){
    node.setColor(color);
    }

    private RBNode<T> parent(RBNode<T> node){
    return node.getParent();
    }

    private RBNode<T> left(RBNode<T> node){
    return node.getLeft();
    }

    private RBNode<T> right(RBNode<T> node){
    return node.getRight();
    }
}

2.左旋转、右旋转

红黑树的旋转操作比AVL少,主要分为左旋和右旋操作,由于红黑树在调整节点颜色的时候,需要经常访问父节点,祖先节点,叔叔节点,兄弟节点,因此每一个节点都记录了它的parent,用于从当前节点向上访问节点,如下:

左旋操作:

private void leftRotate(RBNode<T> node){
        RBNode<T> child = node.getRight();
        child.setParent(node.getParent());
        if(node.getParent() == null){
            this.root = child;
        } else {
            if(node.getParent().getLeft() == node){
                node.getParent().setLeft(child);
            } else {
                node.getParent().setRight(child);
            }
        }

        node.setRight(child.getLeft());
        if(child.getLeft() != null){
            child.getLeft().setParent(node);
        }

        child.setLeft(node);
        node.setParent(child);
    }

右旋操作:

private void rightRotate(RBNode<T> node){
        RBNode<T> child = node.getLeft ();
        child.setParent (node.getParent ());
        if(node.getParent () == null){
            this.root= child;
        } else {
            if(node.getParent().getLeft() == node){
                node.getParent().setLeft(child);
            } else {
                node.getParent().setRight(child);
            }
        }

        node.setLeft (child.getRight ());
        if(child.getRight () != null){
            child.getRight ().setParent (node);
        }

        node.setRight (node);
        node.setParent (child);
    }

3.插入

红黑树的根节点是黑色,其它新插入的节点都是红色节点,因为红色节点不影响红黑树的性质,不会影响某一个路径上黑色节点的数量:

   树是空的,第一个插入的根节点是黑色

   树不是空的,新插入的节点颜色为红色,然后向上检查父节点,父节点为黑色,插入完成;父节点为红色(隐含祖先节点为黑色),需要做插入调整(红黑树的插入调整,需要看叔叔的颜色),以下为三种调整情况:

1.父节点为红色,叔叔节点也是红色

a.把父节点和叔叔节点都设置成黑色

b.把祖父节点设置成红色

c.指针指向祖先节点

d.然后从祖父节点开始继续向上回溯,判断当前指向节点的父节点是否为红色,如果为红色重复操作,反之插入成功,代码结束

 

 

2.父节点为红色,叔叔节点是黑色,当前节点和父节点,祖父节点在一侧(一条线上)

a.将父节点颜色置为黑色

b.将祖先节点颜色置为红色

c.进行右旋操作(这里我们都是以插入节点在左子树上为例,若是在右子树上则进行左旋操作)

3.父节点为红色,叔叔节点是黑色,当前节点和父节点,祖父节点不在同一侧

a.以父亲节点为根节点进行左旋,使当前节点和父节点,祖先节点在同一侧

b.执行情况2操作

只有将所有的情况都判断,才能完成红黑树的插入,代码如下:

    public void insert(T data){
        if(this.root == null){
            this.root = new RBNode<> (data,null,null,null,Color.BLACK);
            return;
        }
        RBNode<T> parent = null;
        RBNode<T> cur = this.root;
        while (cur != null){
            parent = cur;
            if(cur.getData ().compareTo (data) > 0){
                cur = cur.getLeft ();
            }else if(cur.getData ().compareTo (data) < 0){
                cur = cur.getRight ();
            }else {
                return;
            }
        }
        //判断应该插在叶子结点的哪边
        RBNode<T> node = new RBNode<> (data,null,null,null,Color.RED);
        if(parent.getData ().compareTo (data) > 0){
            parent.setLeft (node);
        }else {
            parent.setRight (node);
        }
        //如果新插入节点的父节点是红色,需要做插入调整
        if (color (parent(node)) == Color.RED){
            fixAfterInsert(node);
        }
    }

    private void fixAfterInsert(RBNode<T> node) {
        while(color(parent(node)) == Color.RED){
            if(left(parent(parent(node))) == parent(node)){
                // 当前节点,父节点在祖先节点的左子树
                RBNode<T> uncle = right(parent(parent(node)));
                if(color(uncle) == Color.RED){ // 插入情况1
                    setColor(parent(node), Color.BLACK);
                    setColor(uncle, Color.BLACK);
                    setColor(parent(parent(node)), Color.RED);
                    node = parent(parent(node));
                } else {
                    if(node == right(parent(node))){ // 插入情况3前半段
                        node = parent(node);
                        leftRotate(node);
                    }

                    setColor(parent(node), Color.BLACK); // 插入情况3后半段和情况2合并
                    setColor(parent(parent(node)), Color.RED);
                    rightRotate(parent(parent(node)));
                    break;
                }
            } else {
                // 当前节点,父节点在祖先节点的右树
                RBNode<T> uncle = left(parent(parent(node)));
                if(color(uncle) == Color.RED){ // 插入情况1
                    setColor(parent(node), Color.BLACK);
                    setColor(uncle, Color.BLACK);
                    setColor(parent(parent(node)), Color.RED);
                    node = parent(parent(node));
                } else {
                    if(node == left(parent(node))){ // 插入情况3前半段
                        node = parent(node);
                        rightRotate(node);
                    }

                    setColor(parent(node), Color.BLACK); // 插入情况3后半段和情况2合并
                    setColor(parent(parent(node)), Color.RED);
                    leftRotate(parent(parent(node)));
                    break;
                }
            }
        }
        // 有可能向上回溯到根节点跳出,把根节点置成黑色
        setColor(this.root, Color.BLACK);
    }

测试代码

public class RBTreeTestUnit {
    public static void main(String[] args) {
        RBTree<Integer> avl = new RBTree<> ();
        for (int i = 1; i <= 10; i++) {
            avl.insert (i);

        }
        System.out.println(avl);

    }
}

1~10十个数字,使用红黑树插入图解:

由于夜太深,本博客就先介绍到这,我们将在下一篇博客中继续介绍红黑树的删除操作(较为复杂)

 

 

 

 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值