手撕红黑树

提示

这篇博客主要是我对红黑树一些认识,如果你想较全面的了解红黑树推荐下面这两个,算法4和算法导论也有较全面的讲解。

  1. 美团技术团队
  2. 30张图带你彻底理解红黑树

本文主要参考

初识红黑树

红黑树和2-3树是等价的,基于2-3树的红黑树是一种特殊的红黑树——左倾红黑树,我觉得基于2-3树来理解红黑树,比较好理解,毕竟红黑树的发明人Robert Sedgewick 在其经典著作《算法4》也是用2-3树来引入红黑树的。2-3树是完美平衡的树结构。2–3树的查找元素操作与二叉搜索树的查找类似。因为节点中的数据元素都是有序的,所以查找函数可以据此进入正确的子树进行查找,最终找到正确的节点。进行插入操作时,可以先通过查找操作确定合适的位置,然后将数据插入对应节点。如果插入后的节点变成4节点(包含三个数据元素),则需将该节点拆分为两个2节点,中间的数据元素进入父节点。这样一来,该父节点也可能也会因此变成4节点,则该父节点也会拆分为两个2节点,中间的数据元素进入该父节点的父节点,以此类推,直到修改后的父节点不需要分裂,或者被拆分的是根节点,此时中间数据元素就会单独形成2节点,成为新的根节点。
图片来自维基百科
左倾红黑树的红节点对应的就是2-3树的3节点的左节点,红黑树是保持“黑平衡”的二叉树,严格意义上讲,不是平衡二叉树,最大高度为 2logn,高度的复杂度为O(logn)
图片截自刘宇波老师数据结构的视频很nice的一门课
图片截自刘宇波老师数据结构的视频很nice的一门课
红黑树的性质

  1. 每个节点或者是红色的,或者是黑色的。
  2. 根节点是一定是黑色的,2-3树中,当根节点是二节点的时候明显对应为黑色,当跟节点是三节点的时候,红黑树中对应的红节点就跑到坐下角了。
  3. 每一个叶子节点(指最后的空节点,并不指左右节点都为空的那个节点)是黑色的相当于定义了空节点本身就是一个黑色的节点
  4. 每个红色结点的两个子结点一定都是黑色。(父子节点之间不能出现两个连续的红节点)
  5. 任意一结点到每个叶子结点的路径都包含数量相同的黑结点。
    开始手撕红黑树(代码是跟着刘宇波老师的课程实现的)
    结点定义
class Node<T extends Comparable<T>> {
    private T value;//node value
    private Node<T> left;//left child pointer
    private Node<T> right;//right child pointer
    private Node<T> parent;//parent pointer
    private boolean red;//color is red or not red

    public Node(){}
    public Node(T value){this.value=value;}
    public Node(T value,boolean isRed){this.value=value;this.red = isRed;}

    public T getValue() {
        return value;
    }
    void setValue(T value) {
        this.value = value;
    }
    Node<T> getLeft() {
        return left;
    }
    void setLeft(Node<T> left) {
        this.left = left;
    }
    Node<T> getRight() {
        return right;
    }
    void setRight(Node<T> right) {
        this.right = right;
    }
    Node<T> getParent() {
        return parent;
    }
    void setParent(Node<T> parent) {
        this.parent = parent;
    }
    boolean isRed() {
        return red;
    }
    boolean isBlack(){
        return !red;
    }
    /**
     * is leaf node
     **/
    boolean isLeaf(){
        return left==null && right==null;
    }

    void setRed(boolean red) {
        this.red = red;
    }

    void makeRed(){
        red=true;
    }
    void makeBlack(){
        red=false;
    }
    @Override
    public String toString(){
        return value.toString();
    }
}

左旋转
在这里插入图片描述

    /**左旋转
     *  node              x
     *  / \   左旋转     /   \
     * t1 x ---------> node t3
     *   / \           / \
     *  t2 t3         t1 t2
     * */
    private Node leftRotate(Node node) {
        Node x = node.right;
        Node t2 = x.right;

        // 左旋转
        x.left = node;
        node.right = t2;

        x.color = node.color; // x等于原来树的根节点
        // 2-3树中,添加节点都是红节点,旋转交换之后,也必须
        // 保证这个特性。所以要把node变为红色!(以2-3树举个例子:
        // 一颗树先只有根节点为黑2,现在添加节点红4,对应到红黑树,
        // 根节点就要变成黑4,左子树就要变成红2!)
        node.color = RED;

        return x;
    }

右旋转
在这里插入图片描述
在这里插入图片描述

	/**右旋转
     *    node            x
     *   /  \   右旋转    / \
     *  x   t2 -------> y node
     * / \                / \
     * y t1              t1 t2
     * */
    private Node rightRotate(Node node) {
        Node x = node.left;

        // 右旋转
        node.left = x.right;
        x.right = node;

        // 维护颜色
        x.color = node.color;
        node.color = RED;

        return x;
    }

颜色翻转
在这里插入图片描述

	// 颜色翻转,向3节点添加一个节点(节点对应的位置在右子树,
    // 子节点变黑,父节点变红和上层进行融合)
    private void flipColors(Node node) {
        node.color = RED;
        node.left.color = BLACK;
        node.right.color = BLACK;
    }

添加节点
在这里插入图片描述
由于我们使用的是简化版的左倾红黑树,对于某些场景还需要特殊处理

  1. 场景3,当插入节点为父节点的左节点时,直接插入;当插入节点为父节点的右节点时,需要进行左旋转。
  2. 场景4.1 不存在。
	// 向红黑树中添加新的元素(key, value)
    public void add(K key, V value){
        root = add(root, key, value);
        root.color = BLACK; // 保持最终的根节点为黑色
    }

    // 向以node为根的红黑树中插入元素(key, value),递归算法
    // 返回插入新节点后红黑树的根
    private Node add(Node node, K key, V value){

        if(node == null){
            size ++;
            return new Node(key, value);
        }

        if(key.compareTo(node.key) < 0)
            node.left = add(node.left, key, value);
        else if(key.compareTo(node.key) > 0)
            node.right = add(node.right, key, value);
        else // key.compareTo(node.key) == 0
            node.value = value;

        // 维护红黑树!!!!
        // 左旋转(对应两种情况!)
        if(isRed(node.right) && !isRed(node.left))
            node = this.leftRotate(node.left);

        // 右旋转
        if(isRed(node.left) && isRed(node.left.left))
            node = this.rightRotate(node.left);

        // 颜色翻转
        if(isRed(node.left) && isRed(node.right))
            this.flipColors(node);

        return node;
    }

左倾红黑树相对来说比较容易理解,但是为了维护“左倾”这个性质,做了额外的事情,消耗了性能,没有“任何不平衡都可以在三次旋转内解决”这么好的性能优势。
删除节点
在这里插入图片描述
删除节点的过程,参考美团技术团队(这个实现的是优化的红黑树)
红黑树其他的功能实现跟其他平衡二叉树差不多。
参考

  1. 刘宇波老师的玩转数据结构
  2. 30张图带你彻底理解红黑树
  3. 美团技术团队
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值