红黑树

168 阅读4分钟

1. 红黑树的定义

红黑树是含有红黑链接并满足下列条件的二叉查找树:

  1. 红链接均为左连接
  2. 没有任何一个节点同时和两条红链接相连
  3. 该树是完美的黑色平衡,即任意空链接到根节点的路径上的黑链接数量相同

2-3树和红黑树的对应关系

interview-7-02.png

2.红黑树的API

2.1 节点类

因为每个节点都有一个指向自己的链接(从它的父节点指向它),我们可以在之前的Node节点中添加一个布尔类型的变量color来表示链接的颜色。如果指向它的链接是红色的,那么该变量为true,如果指向它的链接是黑色的,那么该变量就是false

 private class Node<Key, Value> {
        //存储key
        private Key key;
        //存储value
        private Value value;
        //左子节点
        private Node left;
        //右子节点
        private Node right;
        //由其父结点指向它的链接的颜色
        private boolean color;

       public Node(Key key, Value value, Node left, Node right, boolean color) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
            this.color = color;
        }
    }

2.2 平衡化

左旋:

当某个节点的左子节点为黑色,右子节点为红色,此时需要左旋。

*左旋是将某个节点旋转为其右孩子的左孩子*

**前提:**当前节点为h,它的右子节点为x

左旋的过程:

1.让x的左子结点变为h的右子结点:h.right=x.left;

2.让h成为x的左子结点:x.left=h;

3.让h的color属性变为x的color属性值:x.color=h.color;

4.让h的color属性变为RED:h.color=true;

屏幕截图 2021-06-24 211253.png

右旋:

当某个结点的左子结点是红色,且左子结点的左子结点也是红色,需要右旋

**右旋是某个节点旋转为其左孩子的右孩子**

**前提:**当前结点为h,它的左子结点为x;

右旋的过程:

  1. 让x的右子结点成为h的左子结点:h.left = x.right;

  2. 让h成为x的右子结点:x.right=h;

  3. 让x的color变为h的color属性值:x.color = h.color;

  4. 让h的color为RED;

屏幕截图 2021-06-24 211214.png

右旋后左右链接都为红色,需要进行颜色反转

2.3 代码

/**
 * @Author blackcat
 * @create 2021/6/24 20:39
 * @version: 1.0
 * @description:
 */
public class RedBlackTree<Key extends Comparable<Key>, Value> {

    //根节点
    private Node root;
    //记录树中元素的个数
    private int N;
    //红色链接
    private static final boolean RED = true;
    //黑色链接
    private static final boolean BLACK = false;

    class Node {
        //存储key
        private Key key;
        //存储value
        private Value value;
        //左子节点
        private Node left;
        //右子节点
        private Node right;
        //由其父结点指向它的链接的颜色
        private boolean color;

       public Node(Key key, Value value, Node left, Node right, boolean color) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
            this.color = color;
        }
    }

    //判断当前结点的父指向链接是否为红色
    private boolean isRed(Node x) {
        if (x == null) {
            return false;
        }
        return x.color == RED;
    }

    //左旋调整
    private Node rotateLeft(Node h) {
        //找出当前结点h的右子结点
        Node x = h.right;
        //让x的左子结点变为h的右子结点
        h.right = x.left;
        //让h成为x的左子结点
        x.left = h;
        //让h的color属性变为x的color属性值
        x.color = h.color;
        //让当前结点h的color变为RED
        h.color = RED;
        return x;
    }

    //右旋调整
    private Node rotateRight(Node h) {
        //获取左子节点
        Node x = h.left;
        //让x的右子结点成为h的左子结点
        h.left = x.right;
        //让h成为x的右子结点
        x.right = h;
        //让x的color变为h的color属性值
        x.color = h.color;
        //让h的color为RED
        h.color = RED;
        return x;
    }

    //颜色反转,相当于完成拆分4-结点
    private void flipColors(Node h) {
        //当前结点的color属性值变为RED;
        h.color = RED;
        //当前结点的左右子结点的color属性值都变为黑色
        h.left.color = BLACK;
        h.right.color = BLACK;
    }

    //在整个树上完成插入操作
    public void put(Key key, Value val) {
        put(root, key, val);
        //让根结点的颜色变为BLACK
        root.color = BLACK;
    }

    //在指定树中,完成插入操作,并返回添加元素后新的树
    private Node put(Node h, Key key, Value val) {
        //判断h是否为空,如果为空则直接返回一个红色的结点就可以了
        if (h == null) {
            //数量+1
            N++;
            return new Node(key, val, null, null, RED);
        }

        //比较h结点的键和key的大小
        int cmp = key.compareTo(h.key);
        if (cmp < 0) {
            //继续往左
            h.left = put(h.left, key, val);

        } else if (cmp > 0) {
            //继续往右
            h.right = put(h.right, key, val);

        } else {
            //发生值的替换
            h.value = val;
        }

        //进行左旋:当当前结点h的左子结点为黑色,右子结点为红色,需要左旋
        if (isRed(h.right) && !isRed(h.left)) {
            h = rotateLeft(h);
        }

        //进行右旋:当当前结点h的左子结点和左子结点的左子结点都为红色,需要右旋
        if (isRed(h.left) && isRed(h.left.left)) {
            rotateRight(h);
        }

        //颜色反转:当前结点的左子结点和右子结点都为红色时,需要颜色反转
        if (isRed(h.left) && isRed(h.right)) {
            flipColors(h);
        }
        return h;
    }

    //根据key,从树中找出对应的值
    public Value get(Key key) {
        return get(root, key);
    }

    //从指定的树x中,找出key对应的值
    private Value get(Node x, Key key) {
        if (x == null) {
            return null;
        }
        int cmp = key.compareTo(x.key);
        if (cmp > 0) {
            //如果新结点的key大于当前结点的key,则继续找当前结点的右子结点
            return get(x.right, key);
        } else if (cmp < 0) {
            //如果新结点的key小于当前结点的key,则继续找当前结点的左子结点
            return get(x.left, key);
        } else {
            //如果新结点的key等于当前结点的key,则直接返回
            return x.value;
        }
    }

    //获取树中元素的个数
    public int size() {
        return N;
    }
}