《玩转数据结构 从入门到进阶》红黑树

本文来源于liuyubobobo的“玩转数据结构 从入门到进阶”视频教程

本教程是基于二分搜索树实现红黑树,请先看  《玩转数据结构 从入门到进阶》二分搜索树 Binary Search Tree

红黑树也有左旋转、右旋转这种操作,如果不了解,请先阅读 《玩转数据结构 从入门到进阶》平衡二叉树AVL

由于红黑树的定义太过于复杂,所以先学习2-3树,然后通过红黑树与2-3树对比,才能更好的理解红黑树。

2-3树

2-3树满足二分搜索树的基本性质,但其节点可以存放一个元素或者两个元素,2-3是一颗绝对平衡的树。

下图是一颗2-3树

下面用图展示向2-3树加入节点的过程

可以总结出一个规律,给2-3树添加一个节点,此节点必然是首先和某个节点融合。如果直接把新节点加入成某个节点的子节点,那必然会破坏树的绝对平衡。

下图展示把6加入到树中的过程

下图展示把5加入到树中的过程

了解了2-3树之后,就可以来学习红黑树了。先用2-3树和红黑树做一个类比

通过2-3树和红黑树的类比,就大致能理解红黑树是一个什么东西了,下面给出红黑树的定义。

红黑树是每个节点都带有颜色属性的二叉查找树,颜色为红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求

1、节点是红色或者黑色
2、根节点是黑色
3、所有叶子节点(最后的空节点)都是黑色
4、如果一个节点是红色,那么它的孩子节点都是黑色
5、从任意一个节点到子节点,经过的黑色节点个数是相同的

3、5不容易理解,下面用图解释

3、所有叶子节点(最后的空节点)都是黑色

5、从任意一个节点到子节点,经过的黑色节点个数是相同的

本教程实现的红黑树是以红色节点左倾斜为基础的红黑树。

使用《玩转数据结构 从入门到进阶》二分搜索树 Binary Search Tree 中的代码为基础,编写红黑树代码

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

    // 定义红黑树颜色
    private static final boolean RED = true;
    private static final boolean BLACK = false;

    private class Node{
        public K key;
        public V value;
        public Node left, right;
        public boolean color;

        public Node(K key, V value){
            this.key = key;
            this.value = value;
            left = null;
            right = null;
            /**
             * 初始化一个红黑树节点,首先将节点设置为红色,
             * 在将节点添加到树中时,可能会将节点颜色改成黑色
             */
            color = RED;
        }
    }

    private Node root;
    private int size;

    public RBTree(){
        root = null;
        size = 0;
    }

    public int getSize(){
        return size;
    }

    public boolean isEmpty(){
        return size == 0;
    }
}

添加节点的过程

左旋转代码

    //   node                     x
    //  /   \     左旋转         /  \
    // T1   x   --------->   node   T3
    //     / \              /   \
    //    T2 T3            T1   T2
    private Node leftRotate(Node node){
        Node x = node.right;
        // 左旋转
        node.right = x.left;
        x.left = node;
        x.color = node.color;
        node.color = RED;
        /**
         * 若出现x.color = node.color=RED; node.color = RED; 的情况
         * 由于我们已经将x节点返回给调用者,调用者就可以处理x的颜色了
         */
        return x;
    }

颜色翻转代码

    // 颜色翻转
    private void flipColors(Node node){
        node.color = RED;
        node.left.color = BLACK;
        node.right.color = BLACK;
    }

右旋转

右旋转代码

    //     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;
    }

需要左右旋转+颜色翻转的情况

红黑树的添加节点代码(删除代码太复杂,不写了)

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

    // 定义红黑树颜色
    private static final boolean RED = true;
    private static final boolean BLACK = false;

    private class Node{
        public K key;
        public V value;
        public Node left, right;
        public boolean color;

        public Node(K key, V value){
            this.key = key;
            this.value = value;
            left = null;
            right = null;
            /**
             * 初始化一个红黑树节点,首先将节点设置为红色,
             * 在将节点添加到树中时,可能会将节点颜色改成黑色
             */
            color = RED;
        }
    }

    private Node root;
    private int size;

    public RBTree(){
        root = null;
        size = 0;
    }

    public int getSize(){
        return size;
    }

    public boolean isEmpty(){
        return size == 0;
    }

    // 判断节点node的颜色
    private boolean isRed(Node node){
        if(node == null)
            return BLACK;
        return node.color;
    }

    // 返回以node为根节点的二分搜索树中,key所在的节点
    private Node getNode(Node node, K key){

        if(node == null)
            return null;

        if(key.equals(node.key))
            return node;
        else if(key.compareTo(node.key) < 0)
            return getNode(node.left, key);
        else // if(key.compareTo(node.key) > 0)
            return getNode(node.right, key);
    }

    public boolean contains(K key){
        return getNode(root, key) != null;
    }

    public V get(K key){

        Node node = getNode(root, key);
        return node == null ? null : node.value;
    }

    public void set(K key, V newValue){
        Node node = getNode(root, key);
        if(node == null)
            throw new IllegalArgumentException(key + " doesn't exist!");

        node.value = newValue;
    }

    //   node                     x
    //  /   \     左旋转         /  \
    // T1   x   --------->   node   T3
    //     / \              /   \
    //    T2 T3            T1   T2
    private Node leftRotate(Node node){
        Node x = node.right;
        // 左旋转
        node.right = x.left;
        x.left = node;
        x.color = node.color;
        node.color = RED;
        /**
         * 若出现x.color = node.color=RED; node.color = RED; 的情况
         * 由于我们已经将x节点返回给调用者,调用者就可以处理x的颜色了
         */
        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;
    }

    // 颜色翻转
    private void flipColors(Node node){
        node.color = RED;
        node.left.color = BLACK;
        node.right.color = BLACK;
    }

    // 以二分搜索树添加方法为基础改造为红黑树的添加方法
    // 向红黑树中添加新的元素(key, value)
    public void add(K key, V value){
        root = add(root, key, value);
        root.color = BLACK; // 保持红黑树根节点一直为黑色
    }

    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
            node.value = value;

        // 根据条件节点后的逻辑链条,编写处理情况
        // 是否左旋转
        if (isRed(node.right) && !isRed(node.left))
            node = leftRotate(node);
        // 是否右旋
        if (isRed(node.left) && isRed(node.left.left))
            node = rightRotate(node);
        // 是否颜色和翻转
        if (isRed(node.left) && isRed(node.right))
            flipColors(node);

        // 这3个判断条件不是if-else的关系,判断顺序也不能变

        // 由于是递归代码,假如返回的node是红色,则node返回给调用者后,还会执行上面的3个判断
        return node;
    }

    public static void main(String[] args){
        /**
         * 读取傲慢与偏见这本书,通过 “单词”:“单词在书中出现的次数” 这种key-value的形式把书中的单词-词频加到AVLTree中
         * FileUtil、傲慢与偏见.txt 可以到我的github下载
         * https://github.com/CodingSoldier/java-learn/tree/master/note/src/main/java/com/datastructure
         */
        ArrayList<String> words = new ArrayList<>();
        if(FileUtil.readFile("./note/src/main/java/com/datastructure/傲慢与偏见.txt", words)) {
            System.out.println("总单词数: " + words.size());
            RBTree<String, Integer> map = new RBTree<>();
            for (String word : words) {
                if (map.contains(word))
                    map.set(word, map.get(word) + 1);
                else
                    map.add(word, 1);
            }
            System.out.println("单词去重后的总数: " + map.getSize());
        }
    }
}

 红黑树的最大高度是2logN(N是树的size),查询性能比AVL差。但是新增、删除性能要比AVL好。总体来说红黑树的综合性能比AVL好。

发布了51 篇原创文章 · 获赞 14 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/u010606397/article/details/99733509