红黑树

  • 红黑树是二叉搜索树的一种
  • 红黑树的性质:
  1. 每个节点的颜色要么为红色,要么为黑色;
  2. 根节点是黑色的;
  3. 叶子节点是黑色的(根据《算法导论》一书,我们把最底层的结点下面两个空节点视为叶子节点,成为Nil);
  4. 任意两个红色节点不能为父子关系;
  5. 每个节点到其后代叶子节点的黑高相同。(由于该特性导致红黑树是一种近似平衡的二叉树)
  • 黑高(hb):从某个节点x出发(不含该节点)到达一个叶子节点的任意一条简单路径上黑色节点的个数
  • 为实现红黑树功能,先定义了以下树节点的信息:
public class RBTNode<T extends Comparable> {

    private boolean color;
    private T value;
    private RBTNode left;
    private RBTNode right;
    private RBTNode parent;

    public RBTNode(T value) {
        this.color = Color.BLACK;
        this.value = value;
        this.left = null;
        this.right = null;
        this.parent = null;
    }

    public boolean getColor() {
        return color;
    }

    public void setColor(boolean color) {
        this.color = color;
    }

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }

    public RBTNode getLeft() {
        return left;
    }

    public void setLeft(RBTNode left) {
        this.left = left;
    }

    public RBTNode getRight() {
        return right;
    }

    public void setRight(RBTNode right) {
        this.right = right;
    }

    public RBTNode getParent() {
        return parent;
    }

    public void setParent(RBTNode parent) {
        this.parent = parent;
    }

    @Override
    public String toString() {
        return "RBTNode{" +
                "color=" + (color==Color.RED?"RED":"BLACK" )+
                ", value=" + value +
                '}';
    }
}
  • 红黑树类的定义如下:
public class RBTree<T> {

    private RBTNode root;

    public RBTNode getRoot() {
        return root;
    }

    public void setRoot(RBTNode root) {
        this.root = root;
    }
    
    public void insert(RBTNode node);
    public void insert(T value);
    public void leftRotation(RBTNode node);
    public void rightRotation(RBTNode node);
    public void fixUp(RBTNode node);   
    
}
  • insert操作
    向红黑树中插入新节点。新加的节点默认为红色,由于新加的节点可能会产生“两个红色节点为父子关系”的现象,需要经过左旋、右旋以及修改颜色的手段来维持红黑树的性质。为了方便插入新的节点,提供两种新增节点的方法:
    public void insert(RBTNode node) {

        RBTNode parent = null;//记录当前遍历节点的父节点
        RBTNode current = this.getRoot();

        int cmp; //记录value比较的结果

        //按照二叉搜索树的插入方式插入节点node,新插入的结点必然是叶子节点或是根节点
        while(current!=null) {
            cmp = node.getValue().compareTo(current.getValue());
            parent = current;
            if(cmp<0) {
                current = current.getLeft();
            } else {
                current = current.getRight();
            }
        }
        //找到新节点该插入的位置,将其父节点锁定
        node.setParent(parent);
        if(parent==null) {  //是空树
            this.setRoot(node);
        } else {
            if(node.getValue().compareTo(parent.getValue())<0) {
                parent.setLeft(node);
            } else {
                parent.setRight(node);
            }
        }

        //新加的节点颜色默认是红色
        node.setColor(Color.RED);

        //进行颜色的修改
        fixUp(node);
    }

    public void insert(T value) {
        RBTNode node = new RBTNode((Comparable)value);
        if(node!=null) {
            insert(node);
        }
    }
  • 左旋操作
    public void leftRotation(RBTNode node) {

        //获取node的右孩子
        RBTNode right = node.getRight();

        //调整right的左孩子:将right的左孩子改到node的右孩子,此时需要修改right的左孩子节点的父节点的信息
        node.setRight(right.getLeft());
        if(right.getLeft()!=null) {
            right.getLeft().setParent(node);
        }

        //调整node与right的位置关系
        right.setParent(node.getParent());
        if(node.getParent()==null) {
            this.setRoot(right);
        } else {
            if(node.getParent().getLeft()==node) {
                node.getParent().setLeft(right);
            } else {
                node.getParent().setRight(right);
            }
        }

        right.setLeft(node);
        node.setParent(right);

    }
  • 右旋操作
    public void rightRotation(RBTNode node) {

        //获取node的左节点
        RBTNode left = node.getLeft();

        //调整left的右节点为node的左节点
        node.setLeft(left.getRight());
        if(left.getRight()!=null) {
            left.getRight().setParent(node);
        }

        //调整node与left的关系
        left.setParent(node.getParent());
        if(node.getParent().getLeft()==node) {
            node.getParent().setLeft(left);
        } else {
            node.getParent().setRight(left);
        }
        left.setRight(node);
        node.setParent(left);

    }
  • 修改颜色信息

    也就是出现了“两红色节点是父子关系”这种现象,需要分成以下几种情况来讨论:

  1. 父节点与叔叔节点同为红色,那么直接将叔叔节点和父亲节点修改为黑色,祖父节点改为红色即可;
  2. 父亲节点为红色,叔叔节点为黑色
    (1)父亲节点为左孩子,当前节点为左孩子:父节点变黑,祖父节点变红后右旋;
    (2)父亲节点为左孩子,当前节点为右孩子:父节点左旋,转(1);
    (3)父亲节点为右孩子,当前节点为左孩子:父节点右旋,转(4);
    (4)父亲节点为右孩子,当前节点为右孩子:父节点变黑,祖父节点变红后左旋;

注意:由于需要保证根节点是黑色的,而在修改颜色的过程中可能会将根节点变红,所以需要特别设置一下根节点的颜色。

    public void fixUp(RBTNode node) {
        //如果是根节点,则直接改为黑色
        if(this.getRoot()==node) {
            node.setColor(Color.BLACK);
            return;
        }

        //修改颜色信息与父节点以及叔叔节点有关
        RBTNode parent,uncle;

        //由于新加入的节点是红色的,不会影响黑高,需要调整。按照算法导论中的意思,所有叶子节点都在最后一层,全是黑色,但是没有value
        //所以新加入的结点都不会是叶子节点,需要调整颜色信息只是因为其与父节点颜色冲突(同时为红色)
        //此处需要用循环,因为在修改颜色的过程中,可能会产生新的冲突
        parent = node.getParent();
        while(parent.getColor()==Color.RED) { //如果是红色,则该节点的肯定不会为根节点,此时需要判别该节点的叔叔节点的颜色信息

            //parent是祖父结点的左孩子
            if(parent==parent.getParent().getLeft()) {
                //获取叔叔节点信息
                uncle = parent.getParent().getRight();

                //case1 :叔叔节点的颜色也是红色,那么只需要将父节点和叔叔节点改为黑色。
                // 由于红色节点的父节点必为黑色节点,为了保持黑高,所以将祖父结点的颜色改为红色
                if(uncle!=null&&uncle.getColor()==Color.RED) {
                    parent.setColor(Color.BLACK);
                    uncle.setColor(Color.BLACK);
                    parent.setColor(Color.RED);
                    //看看祖父节点是否产生颜色冲突
                    parent = parent.getParent();
                    continue;
                }

                //case2 : 叔叔节点的颜色是黑色,node是右孩子
                //parent节点进行一次左旋(拉直),然后进行case3的右旋,但是需要调整节点信息
                if(parent.getRight()==node) {
                    leftRotation(parent);
                    //左旋之后父子关系发生变化,需要修改才能正确执行case3的情形
                    RBTNode tmp = parent;
                    parent = node;
                    node = tmp;
                }

                //case3 : 叔叔节点的颜色是黑色,node是左孩子
                //gparent节点进行一次右旋
                parent.setColor(Color.BLACK);
                parent.getParent().setColor(Color.RED);
                rightRotation(parent.getParent());


            } else {  //parent节点是祖父节点的右孩子,需要进行对称操作
                //获取叔叔节点信息
                uncle = parent.getParent().getLeft();

                //case1 :叔叔节点的颜色也是红色,那么只需要将父节点和叔叔节点改为黑色。
                // 由于红色节点的父节点必为黑色节点,为了保持黑高,所以将祖父结点的颜色改为红色
                if(uncle!=null&&uncle.getColor()==Color.RED) {
                    parent.setColor(Color.BLACK);
                    uncle.setColor(Color.BLACK);
                    parent.setColor(Color.RED);
                    //看看祖父节点是否产生颜色冲突
                    parent = parent.getParent();
                    continue;
                }

                //case2 : 叔叔节点的颜色是黑色,node是右孩子
                //parent节点进行一次左旋(拉直),然后进行case3的右旋,但是需要调整节点信息
                if(parent.getLeft()==node) {
                    rightRotation(parent);
                    //左旋之后父子关系发生变化,需要修改才能正确执行case3的情形
                    RBTNode tmp = parent;
                    parent = node;
                    node = tmp;
                }

                //case3 : 叔叔节点的颜色是黑色,node是左孩子
                //gparent节点进行一次右旋
                parent.setColor(Color.BLACK);
                parent.getParent().setColor(Color.RED);
                leftRotation(parent.getParent());

            }
        }

        //循环结束后需要保证根节点为黑色
        this.getRoot().setColor(Color.BLACK);

    }
  • 删除操作(未实现,待续…)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值