Java实现+讲解红黑树

2 篇文章 0 订阅
2 篇文章 0 订阅

一、在讲解红黑树之前,我们要先知道一个树叫:”2-3树“

每一个东西的诞生都是因为之前的方法无法满足某些需求了,所以新的东西被研发出来了。首先我们看这个例子:向二叉树中插入9,8,7,6,5,4,3,2,1。如果这样插入的话,那么可以说
这就不是二叉树了,这是一个链表,因为后面插入的数据都比前一个小,那么就一直是左子树。二叉树之所以被发明出来就是为了查找快,但是这种情况查询的速度就不行了,于是,我们想:
能不能解决这样的问题呢?所以有了2-3树,(一个结点有一个子树就叫它几杠结点,在2-3树中,可以一个结点有三个子树,中间的结点比父节点(有两个数据)的第一个数据大比第二个数据小)
当我们插入结点时,还是比根节点小则向左找……,不同的是:插入结点时,找到叶子节点后,如果叶子节点是二杠结点,则升为三杠结点;若为三杠顶点,则升为四杠顶点,但是2-3树中不允许四杠结点存在
就会把这个四杠结点拆分。拆分的具体实现是:将该四杠结点的中间的数据向上提,如果上面有节点,上面的结点同样实现上述步骤,一直到根节点,如果根节点插入后也变成了四杠节点,
那么同理,还是将该结点的中间数据向上提,即树高度+1;
我们其实就是要,减小树的深度

二、2-3树的性质:

因为2-3的二杠结点和三杠节点,所以使得:
1.所有结点到根节点的距离相等
2.除非插入时根节点由三杠结点转为四杠结点,否则树的高度不会变
3.二叉树和2-3树的最大区别是:二叉树的自顶向下生长的,2-3树是自底向上生长的

2-3树的实现非常复杂,在这里不做实现,但是,2-3树的思想非常重要!在红黑树、B树、B+树中,都用到了2-3树的思想
这篇文章,我们实现红黑树:正常的二叉树与两个子树的连接都是黑色的,红黑树就是要实现2-3树,但是由于三杠结点等实现复杂,所以它是这么实现的:它用每个结点和其特殊标记(红色连接)
来代表这个三杠节点,那么由红色连接的两个结点(可以看成是一个结点)就共同组成了一个三杠结点(在画图时我们可以把红链接化成平线,黑链接向下指)。红黑树是二叉树的一个扩展,所以它当然也满足二叉树的性质:左子节点的key小于根节点的key,右子节点的key大于根节点的key

简单示意图:
在这里插入图片描述
平衡化操作:即当红黑树经过增删改查的操作后,红黑树可能就不满足红黑树的定义了,我们需要对该树进行操作,使之依旧满足红黑树的条件
1.左旋:
在这里插入图片描述
2.右旋:
在这里插入图片描述
3.颜色反转:
将右旋过后的(上图)E结点的下面两节点的颜色都变成黑色,E结点本身变成红色

三、上代码:(结点类是红黑树类中的内部类)

public class RedBlackTree<K extends Comparable<K>, V> {

    //根节点
    private Node<K, V> root;
    //结点个数
    private int N;
    //直接true、false可读性不高
    private static final boolean RED=true;
    private static final boolean BLACK=false;

    private class Node<K, V> {
        private K key;
        private V value;
        private Node<K, V> left;
        private Node<K, V> right;
        //我们将指向该结点的连接定义成颜色。值得注意的是:在红黑树中,根节点是一个比较特殊的结点,它的连接一定是黑色的,因为没有任何结点指向它/
        private boolean color;

        public Node(K key, V value, Node<K, V> left, Node<K, V> right, boolean color) {
            this.key=key;
            this.value=value;
            this.left=left;
            this.right=right;
            this.color=color;
        }
    }

    //构造方法
     public RedBlackTree() {
         root = null;
         N=0;
     }

     //返回结点个数
     public int size() {
         return N;
     }

     //判断树是否为空
     public boolean isEmpty() {
         return N==0;
     }

    /**
     * @param h 结点
     * @return 判断指向该结点的连接是否为红色
     */
    public boolean isRed(Node<K, V> h) {
        //如果为null,我们认为其连接是黑色的
        if(h==null) return false;
        return h.color==RED;
    }

    //因为红黑树本身一个种平衡树。所以我们要进行树的平衡化。使整个树平衡(意思就是:在数进行增删改查操作时不破坏红黑树的规则):
    /**
     * @param h h结点的右连接为红,我们需要将该红链接改到左面
     * @return Node 返回更改后满足条件的结点(更改后的结点并不是h了,是h的右子节点)
     * 左旋目的:是为了有4-结点时操作
     *
     * 由于传进来的这个结点应该还是其父节点的,所以我们要返回修改后的结点作为其原来父节点的左/右结点。
     */
     private Node<K, V> rotateLeft(Node<K, V> h) {
         //获取h结点的右子节点s
         Node<K, V> s = h.right;
         //将s的左子节点作为h的右子节点(因为s的左子节点必比h大)
         h.right = s.left;
         //将h作为s的左子节点
         s.left = h;
         //将h的颜色复制给s颜色,因为之前与上面连接的是h,该后就变成s了
         s.color = h.color;
         //h的颜色为红色
         h.color = RED;
         //返回新的结点
         return s;
     }

    /**
     * @param h h结点和其左子结点(s)的连接和 s和s的左子结点的左子树连接都为红,我们需要进行右旋操作
     * @return Node 返回更改后的结点(更改后,返回为:h与其左子结点和其右子结点的连接都为红。但是显然,这样也不行,因为还是构成了一个4-结点,所以我们好需要一步操作:”颜色反转“)
     * 右旋目的:当出现当出现一个结点的两个连接都为红链接时,说明这是一个4-结点,但是红黑树/2-3树不允许4-结点的存在
     * 函数作用:“将h结点和其左子结点(s)的连接和 s和s的左子结点的左子树连接都为红” 修改为: “该结点与其左子结点和其右子结点的连接都为红”
     */
    private Node<K, V> rotateRight(Node<K, V> h) {
        //获取h的左子节点s
        Node<K, V> s = h.left;
        //s的右子节点作为h的左子节点
        h.left = s.right;
        //h作为s的右子节点
        s.right = h;
        //h的颜色赋值给s的颜色
        s.color = h.color;
        //让h的颜色为红色
        h.color = RED;
        //返回修改后满足条件的结点
        return s;
    }

    /**
     * 反转颜色:即将右旋后的结果在进行处理,使其恢复红黑树的规则
     * 这个方法就比较简单了
     * @param h h结点的左右子树连接都为红色时,进行修改
     */
    private void flipColors(Node<K, V> h) {
        //让h的左子节点和右子结点的颜色变成黑色
        h.left.color = BLACK;
        h.right.color = BLACK;
        //h结点的颜色变为红色
        h.color = RED;
    }

     //由于我们想要实现平衡树,所以每次插入都让其连接为红色,然后使整颗树平衡
    public void put(K key, V value) {
        root = put(root, key, value);
        //根节点的颜色总是黑色的,因为没有其他结点指向根节点
        root.color = BLACK;
    }

    /**
     * insert重载方法,向指定树中插入元素
     * @param x 指定树
     * @param key 插入的键
     * @param value 插入的值
     * @return 返回的结点是插入后的根节点(因为插入后可能会引起树根节点的变化)
     */
    private Node<K, V> put(Node<K, V> x, K key, V value) {
        //如果没有元素
        if(x==null) {
            N++;
            return new Node<>(key, value, null, null, RED);
        }

        //判断插入元素与当前结点key值的大小关系,(cmp<0说明前面的小)
        int cmp = x.key.compareTo(key);
        //如果比当前元素小
        if(cmp>0) {
            x. left = put(x.left, key, value);//递归寻找
        } else if(cmp<0) {
            x.right = put(x.right, key, value);//同理
        } else {
            //就是与当前结点的key相等了,我们只需要把value改一下即可
            x.value = value;
            return x;
        }

        //我们需要判断插入后,如果不满足红黑树的定义,我们要把它改成满足红黑树的样子
        if(isRed(x.right) && !isRed(x.left)) {
            x = rotateLeft(x);//左旋
        }

        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 V get(K key) {
        return get(root, key);
    }

    /**
     * get的重载方法,找到某棵树上的键所对应的值
     * @param x 某个结点/树的根节点
     * @param key 键
     * @return 找到的结果
     */
    private V get(Node<K,V> x, K key) {
        if(x==null) return null;

        int cmp = x.key.compareTo(key);
        if(cmp>0) {
            return get(x.left, key);
        } else if(cmp<0) {
            return get(x.right, key);
        } else {
            return x.value;
        }
    }

}

注:上述部分图片来自黑马程序员教程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值