玩转数据结构(20)-- 红黑树

红黑树

史上最清晰的红黑树讲解(上)

史上最清晰的红黑树讲解(下)

一、概述

图示:

《玩转数据结构(20)-- 红黑树》

红黑树是二分搜索树,并在二分搜索树的基础上添加一些性质确保其不会退化为链表,保证其是平衡二叉树

《算法导论》中的具体规定:

1.每个节点要么是红色,要么是黑色;

2.根节点是黑色的

3.每个叶子节点(最后的空节点)是黑色的

4.如果一个节点是红色的,则其两个孩子节点都是黑色的

5.从任意一个节点到叶子节点(最后的空节点),经过的黑色节点是一样的

想要更好地理解 红黑树 ,就要知道什么是 2-3树

2-3 树

1.满足二分搜索树的基本性质

2.不是一种二叉树,它有两种节点,一种可存放一个元素【左孩子<a<右孩子】,一种可存放两个元素【3个孩子,左孩子<b<中间孩子<c<右孩子】

《玩转数据结构(20)-- 红黑树》

3.每个节点有 2 或 3 个 孩子,故称为 2-3 树

图示:

《玩转数据结构(20)-- 红黑树》

2-3 树性质:它是一颗绝对平衡的树【从根节点到任意叶子节点所经过的节点数量一定是相同的,对任意节点,左右子树的高度一定是相等的】

二、树如何维持绝对的平衡

2-3树 中添加节点

1. 42为根节点

《玩转数据结构(20)-- 红黑树》

2.添加元素 37 ,因为 37 < 42 ,所以 37 应放在 42 的左子树中,而由于 42 的左子树为空,【新节点永远不会去 空 的位置,只会和最后找到的叶子节点做融合】新节点 37 将融合到 在添加过程中所找到的最后一个叶子节点上,叶子节点就是 42 这个节点,所以,会发生 节点的融合,由 2节点 变为 3节点

《玩转数据结构(20)-- 红黑树》

3.再添加元素 12 ,因为 12 < 37 ,所以 12 应放在 37 的左子树中,而由于 37 的左子树为空【新节点永远不会去 空 的位置,只会和最后找到的叶子节点做融合】故虽然已经是 3节点,但仍然先融合,暂时形成 4节点

《玩转数据结构(20)-- 红黑树》

4. 2-3树 中最多有 3节点,所以将 4节点 分裂成 一棵子树, 4节点 转换为 由 3个 2节点 组成的一棵平衡的树,如图所示,可以将其理解为二分搜索树与 2-3树,只不过每个节点都是 2节点,同时这棵树保持着绝对的平衡

《玩转数据结构(20)-- 红黑树》

5.在树中再添加新元素 18,根节点是37 ,要将 18 添加到其左子树12 中 ,18 > 12 故将其添加到 12 的右子树中去 ,此时 12 的右子树 已经为空【新节点永远不会去 空 的位置,只会和最后找到的叶子节点做融合】这棵树保持着绝对的平衡

《玩转数据结构(20)-- 红黑树》

6.在树中再添加新元素 6,根节点是37 ,要将 18 添加到其左子树(包含 12 18 的3节点) 中 ,6 < 12 故将其添加到 3节点 的左子树中去 ,此时 3 的左子树 为空【新节点永远不会去 空 的位置,只会和最后找到的叶子节点做融合】,故先融合为 4节点

《玩转数据结构(20)-- 红黑树》

7.将 4节点 拆解,变为 包含 3个 2节点 的子树

《玩转数据结构(20)-- 红黑树》

如果拆分为上图所示,此时的 2-3 树不是 绝对 平衡的树,对 2-3树 来说,如果其叶子节点本身已经是一个 3节点 了,添加一个节点变为 4节点 ,变为如图所示,则 12 需要与其父节点 37 进行融合,37 是 2节点,12 和 37 可以直接融合为 3节点,此时该树仍然 绝对平衡

《玩转数据结构(20)-- 红黑树》

8.在树中再添加新元素 11,根节点是 12 37 的3节点,要将 11 添加到其左子树6中 ,6 < 11 故将其添加到 6 的右子树中去 ,此时 6 的右子树 为空【新节点永远不会去 空 的位置,只会和最后找到的叶子节点做融合】,故先融合为 3节点

《玩转数据结构(20)-- 红黑树》

9.在树中再添加新元素 5,根节点是 12 37 的3节点,要将 5 添加到其左子树 6 11 的3节点 中 ,5 < 6 故将其添加到 6 11 的3节点左子树中去 ,此时 6 11 的3节点 的左子树 为空【新节点永远不会去 空 的位置,只会和最后找到的叶子节点做融合】,故先融合为 4节点

《玩转数据结构(20)-- 红黑树》

将 4节点 拆解,变为 包含 3个 2节点 的子树

《玩转数据结构(20)-- 红黑树》

如果拆分为上图所示,此时的 2-3 树不是 绝对 平衡的树,对 2-3树 来说,如果其叶子节点本身已经是一个 3节点 了,添加一个节点变为 4节点 ,变为如图所示,则 6 需要与其父节点 12 37的 3节点 进行融合,变为 4节点,此时该树仍然 绝对平衡

《玩转数据结构(20)-- 红黑树》

将 4节点 拆解,变为 包含 3个 2节点 的子树,此时,树仍然 绝对平衡并保持着 2-3树的性质

《玩转数据结构(20)-- 红黑树》

总结:

如果插入 2节点,直接融合为 3节点

《玩转数据结构(20)-- 红黑树》

如果插入 3节点 ,且为根节点,先融合再拆分

《玩转数据结构(20)-- 红黑树》

如果插入 3节点 ,父亲节点为 2节点,先融合再拆分,再融合

《玩转数据结构(20)-- 红黑树》

如果插入 3节点 ,父亲节点为 3节点,先融合再拆分,再融合再拆分

《玩转数据结构(20)-- 红黑树》

三、红黑树与2-3树的等价性

《玩转数据结构(20)-- 红黑树》

表示 b 和 c 是并列关系(一起存放在 3节点 中),则将其 连线 设置为 红色; 将其还原为 二分搜索树中的样子

《玩转数据结构(20)-- 红黑树》

表示 红色连线 的方法:由于每一个节点只有一个父亲(每一个节点和其父亲节点相连接的边只有一条线),可以对 b节点 做特殊标识,使其为红色,表示 b节点 与 c节点 在 2-3树 中是一起存放在 3节点中【所有的红色节点都是左倾斜的】

《玩转数据结构(20)-- 红黑树》

如图所示

《玩转数据结构(20)-- 红黑树》

《玩转数据结构(20)-- 红黑树》 

红黑树性质:

1.每个节点要么是红色,要么是黑色;

2.根节点是黑色的

3.每个叶子节点(最后的空节点)是黑色的【定义 空 为黑色】

4.如果一个节点是红色的,则其两个孩子节点都是黑色的【黑色节点 右孩子 必为 黑色】

5.从任意一个节点到叶子节点(最后的空节点),经过的黑色节点是一样多的【核心】

 分析:2-3树是 绝对平衡的,所以 所有的叶子节点都在同一层上,其深度是一样的;从任意节点出发,有固定的深度,从该节点向下到达(可到达)的叶子节点,向下的深度也是一样的,其经过的节点数量是一样的;对应到 红黑树 中,则经过的黑色节点是一样多的。【红黑树 是保持 “黑平衡” 的二叉树,最大高度:2log n ,时间复杂度:O(log n)】

代码实现:RBTree.java

import java.util.ArrayList;

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

    private static final boolean RED = true;	//定义红色为 true
    private static final boolean BLACK = false;//定义黑色为 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;
    }

    // 向二分搜索树中添加新的元素(key, value)
    public void add(K key, V value){
        root = add(root, key, value);
    }

    // 向以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;

        return node;
    }

    // 返回以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为根的二分搜索树的最小值所在的节点
    private Node minimum(Node node){
        if(node.left == null)
            return node;
        return minimum(node.left);
    }

    // 删除掉以node为根的二分搜索树中的最小节点
    // 返回删除节点后新的二分搜索树的根
    private Node removeMin(Node node){

        if(node.left == null){
            Node rightNode = node.right;
            node.right = null;
            size --;
            return rightNode;
        }

        node.left = removeMin(node.left);
        return node;
    }

    // 从二分搜索树中删除键为key的节点
    public V remove(K key){

        Node node = getNode(root, key);
        if(node != null){
            root = remove(root, key);
            return node.value;
        }
        return null;
    }

    private Node remove(Node node, K key){

        if( node == null )
            return null;

        if( key.compareTo(node.key) < 0 ){
            node.left = remove(node.left , key);
            return node;
        }
        else if(key.compareTo(node.key) > 0 ){
            node.right = remove(node.right, key);
            return node;
        }
        else{   // key.compareTo(node.key) == 0

            // 待删除节点左子树为空的情况
            if(node.left == null){
                Node rightNode = node.right;
                node.right = null;
                size --;
                return rightNode;
            }

            // 待删除节点右子树为空的情况
            if(node.right == null){
                Node leftNode = node.left;
                node.left = null;
                size --;
                return leftNode;
            }

            // 待删除节点左右子树均不为空的情况

            // 找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点
            // 用这个节点顶替待删除节点的位置
            Node successor = minimum(node.right);
            successor.right = removeMin(node.right);
            successor.left = node.left;

            node.left = node.right = null;

            return successor;
        }
    }

    public static void main(String[] args){

        System.out.println("Pride and Prejudice");

        ArrayList<String> words = new ArrayList<>();
        if(FileOperation.readFile("pride-and-prejudice.txt", words)) {
            System.out.println("Total words: " + 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("Total different words: " + map.getSize());
            System.out.println("Frequency of PRIDE: " + map.get("pride"));
            System.out.println("Frequency of PREJUDICE: " + map.get("prejudice"));
        }

        System.out.println();
    }
}

四、 红黑树添加元素和左旋转

红黑树添加元素:

《玩转数据结构(20)-- 红黑树》
 

《玩转数据结构(20)-- 红黑树》

保持 根节点 为 黑色

public void add(K key, V value){
        root = add(root, key, value);
        root.color = BLACK; // 最终根节点为黑色节点
    }

添加过程:整棵树为空时,添加元素 42,使 根 为 42 即可

《玩转数据结构(20)-- 红黑树》

但红黑树中,根节点必须为 黑色 ,将其变为 黑色

《玩转数据结构(20)-- 红黑树》

插入元素 37,插入到 42 的 左孩子的位置,相当于 2-3树 中的 3节点

《玩转数据结构(20)-- 红黑树》

但如果 根节点为 37,要插入元素为 42,则不满足红黑树的定义(红色节点 左倾斜)

《玩转数据结构(20)-- 红黑树》

解决方法:左旋转,上图 经过 左旋转 变为 下图

《玩转数据结构(20)-- 红黑树》

左旋转过程:1.假设根节点为 node,node 的右孩子为 x

《玩转数据结构(20)-- 红黑树》

2.node.right = x.left,让 node 的右孩子 为 x 的左子树, 仍然保持二分搜索树性质

《玩转数据结构(20)-- 红黑树》

3.x.right = node, x 的左子树连接上 node ,完成左旋转,37 从原来 42 的父亲 变为 42 的右孩子,相当于逆时针旋转,在 37 来看是左旋转

《玩转数据结构(20)-- 红黑树》

4.对于红黑树来说,需要维护颜色信息

x.color = node.color ,新的根节点是 x节点,x节点的颜色要和以前的节点 node 的颜色是一样的

《玩转数据结构(20)-- 红黑树》

node 变为 x 的左孩子,对应到 2-3 树中,还是临时的 4节点,要让 node 的颜色变为 红色,node.color = RED;表示它和它的父亲节点 42 是融合在一起的 ;这样就完成了整个左旋转过程

《玩转数据结构(20)-- 红黑树》

代码实现:RBTree.java

import java.util.ArrayList;

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

        return x;
    }

    // 向红黑树中添加新的元素(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;

        return node;
    }

    // 返回以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为根的二分搜索树的最小值所在的节点
    private Node minimum(Node node){
        if(node.left == null)
            return node;
        return minimum(node.left);
    }

    // 删除掉以node为根的二分搜索树中的最小节点
    // 返回删除节点后新的二分搜索树的根
    private Node removeMin(Node node){

        if(node.left == null){
            Node rightNode = node.right;
            node.right = null;
            size --;
            return rightNode;
        }

        node.left = removeMin(node.left);
        return node;
    }

    // 从二分搜索树中删除键为key的节点
    public V remove(K key){

        Node node = getNode(root, key);
        if(node != null){
            root = remove(root, key);
            return node.value;
        }
        return null;
    }

    private Node remove(Node node, K key){

        if( node == null )
            return null;

        if( key.compareTo(node.key) < 0 ){
            node.left = remove(node.left , key);
            return node;
        }
        else if(key.compareTo(node.key) > 0 ){
            node.right = remove(node.right, key);
            return node;
        }
        else{   // key.compareTo(node.key) == 0

            // 待删除节点左子树为空的情况
            if(node.left == null){
                Node rightNode = node.right;
                node.right = null;
                size --;
                return rightNode;
            }

            // 待删除节点右子树为空的情况
            if(node.right == null){
                Node leftNode = node.left;
                node.left = null;
                size --;
                return leftNode;
            }

            // 待删除节点左右子树均不为空的情况

            // 找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点
            // 用这个节点顶替待删除节点的位置
            Node successor = minimum(node.right);
            successor.right = removeMin(node.right);
            successor.left = node.left;

            node.left = node.right = null;

            return successor;
        }
    }

    public static void main(String[] args){

        System.out.println("Pride and Prejudice");

        ArrayList<String> words = new ArrayList<>();
        if(FileOperation.readFile("pride-and-prejudice.txt", words)) {
            System.out.println("Total words: " + 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("Total different words: " + map.getSize());
            System.out.println("Frequency of PRIDE: " + map.get("pride"));
            System.out.println("Frequency of PREJUDICE: " + map.get("prejudice"));
        }

        System.out.println();
    }
}

五、颜色翻转和右旋转

红黑树添加新元素

1.红黑树本身是空的时候,直接将节点添加进来,让其保持 黑色 即可

《玩转数据结构(20)-- 红黑树》

2.添加新节点到左右子树均为空的黑色节点上,如果为 黑节点 的左孩子,直接添加即可

《玩转数据结构(20)-- 红黑树》

3.如果为 黑节点 的右孩子,需要进行左旋转,【将一个新的元素放进 2节点 中,2节点 转换为 3节点在红黑树中的操作】

《玩转数据结构(20)-- 红黑树》

4.向红黑树的 3节点 中添加元素

《玩转数据结构(20)-- 红黑树》

5.添加元素 66 ,则 【红:该节点与其父节点融合在一起,37 和 66 都是红色,均为融合在一起的,可以理解为临时的 4节点】

《玩转数据结构(20)-- 红黑树》

在 2-3树 中相当于如图,4节点拆分为 3个 2节点

《玩转数据结构(20)-- 红黑树》

在红黑树中,4节点拆分为 3个 2节点 代表 3个节点 均为黑色的节点,黑色节点左侧没有 红色节点,则代表单独的 2节点,故

《玩转数据结构(20)-- 红黑树》

在 2-3树 中,如果插入 3节点 ,父亲节点为 2节点,先融合再拆分,再融合,融合在红黑树中为 

《玩转数据结构(20)-- 红黑树》

以上添加元素完成,发现红黑树中节点颜色正好相反,称为 颜色翻转(filpColors)

在下图红黑树中添加元素 12

《玩转数据结构(20)-- 红黑树》

由二分搜索树添加元素法则,知 12 添加到 37 的左孩子中

《玩转数据结构(20)-- 红黑树》

等同于 2-3树,形成临时 4节点

《玩转数据结构(20)-- 红黑树》

4 节点要拆分

《玩转数据结构(20)-- 红黑树》

在红黑树中,需要用到 右旋转

1.假设根节点为 node,node 的左孩子为 x ,

《玩转数据结构(20)-- 红黑树》

2.node.left = T1,让 node 的左子树 为 左孩子的右子树, 仍然保持二分搜索树性质

《玩转数据结构(20)-- 红黑树》

3.x.right = node ,完成右旋转,42 从原来 37的父亲 变为 37的右孩子,相当于顺时针旋转,在 37 来看是右旋转

《玩转数据结构(20)-- 红黑树》

4.对于红黑树来说,需要维护颜色信息

x.color = node.color ,新的根节点是 x节点,x节点的颜色要和以前的节点 node 的颜色是一样的

《玩转数据结构(20)-- 红黑树》

node 变为 x 的右孩子,对应到 2-3树中,还是临时的 4节点,要让 node 的颜色变为 红色,node.color = RED;表示它和它的父亲节点 42 是融合在一起的 ;这样就完成了整个右旋转过程

《玩转数据结构(20)-- 红黑树》

代码实现:颜色翻转

RBTree.java

import java.util.ArrayList;

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

        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; // 最终根节点为黑色节点
    }

    // 向以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;

        return node;
    }

    // 返回以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为根的二分搜索树的最小值所在的节点
    private Node minimum(Node node){
        if(node.left == null)
            return node;
        return minimum(node.left);
    }

    // 删除掉以node为根的二分搜索树中的最小节点
    // 返回删除节点后新的二分搜索树的根
    private Node removeMin(Node node){

        if(node.left == null){
            Node rightNode = node.right;
            node.right = null;
            size --;
            return rightNode;
        }

        node.left = removeMin(node.left);
        return node;
    }

    // 从二分搜索树中删除键为key的节点
    public V remove(K key){

        Node node = getNode(root, key);
        if(node != null){
            root = remove(root, key);
            return node.value;
        }
        return null;
    }

    private Node remove(Node node, K key){

        if( node == null )
            return null;

        if( key.compareTo(node.key) < 0 ){
            node.left = remove(node.left , key);
            return node;
        }
        else if(key.compareTo(node.key) > 0 ){
            node.right = remove(node.right, key);
            return node;
        }
        else{   // key.compareTo(node.key) == 0

            // 待删除节点左子树为空的情况
            if(node.left == null){
                Node rightNode = node.right;
                node.right = null;
                size --;
                return rightNode;
            }

            // 待删除节点右子树为空的情况
            if(node.right == null){
                Node leftNode = node.left;
                node.left = null;
                size --;
                return leftNode;
            }

            // 待删除节点左右子树均不为空的情况

            // 找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点
            // 用这个节点顶替待删除节点的位置
            Node successor = minimum(node.right);
            successor.right = removeMin(node.right);
            successor.left = node.left;

            node.left = node.right = null;

            return successor;
        }
    }

    public static void main(String[] args){

        System.out.println("Pride and Prejudice");

        ArrayList<String> words = new ArrayList<>();
        if(FileOperation.readFile("pride-and-prejudice.txt", words)) {
            System.out.println("Total words: " + 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("Total different words: " + map.getSize());
            System.out.println("Frequency of PRIDE: " + map.get("pride"));
            System.out.println("Frequency of PREJUDICE: " + map.get("prejudice"));
        }

        System.out.println();
    }
}

总结:

1.

《玩转数据结构(20)-- 红黑树》

2.

《玩转数据结构(20)-- 红黑树》

3.

《玩转数据结构(20)-- 红黑树》

这种解决方法:1.左旋转

《玩转数据结构(20)-- 红黑树》

2.再右旋转

《玩转数据结构(20)-- 红黑树》

3.维持颜色

《玩转数据结构(20)-- 红黑树》

4.颜色翻转

《玩转数据结构(20)-- 红黑树》

最复杂情况的添加元素:新添加节点在对应 2-3树 中间

《玩转数据结构(20)-- 红黑树》

对2个红节点左旋转

《玩转数据结构(20)-- 红黑树》

对黑节点右旋转

《玩转数据结构(20)-- 红黑树》

颜色翻转

《玩转数据结构(20)-- 红黑树》

在等价于 2-3树中的 3节点 中添加比这2个元素还要小的元素,则

《玩转数据结构(20)-- 红黑树》

在等价于 2-3树中的 3节点 中添加比这2个元素最大值【黑】还要大的元素,则

《玩转数据结构(20)-- 红黑树》

维护的时机:和 AVL树 一样,添加节点后回溯向上维护

红黑树 【最终版】代码:RBTree.java

import java.util.ArrayList;

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

        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; // 最终根节点为黑色节点
    }

    // 向以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 = leftRotate(node);

        if (isRed(node.left) && isRed(node.left.left))
            node = rightRotate(node);

        if (isRed(node.left) && isRed(node.right))
            flipColors(node);

        return node;
    }

    // 返回以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为根的二分搜索树的最小值所在的节点
    private Node minimum(Node node){
        if(node.left == null)
            return node;
        return minimum(node.left);
    }

    // 删除掉以node为根的二分搜索树中的最小节点
    // 返回删除节点后新的二分搜索树的根
    private Node removeMin(Node node){

        if(node.left == null){
            Node rightNode = node.right;
            node.right = null;
            size --;
            return rightNode;
        }

        node.left = removeMin(node.left);
        return node;
    }

    // 从二分搜索树中删除键为key的节点
    public V remove(K key){

        Node node = getNode(root, key);
        if(node != null){
            root = remove(root, key);
            return node.value;
        }
        return null;
    }

    private Node remove(Node node, K key){

        if( node == null )
            return null;

        if( key.compareTo(node.key) < 0 ){
            node.left = remove(node.left , key);
            return node;
        }
        else if(key.compareTo(node.key) > 0 ){
            node.right = remove(node.right, key);
            return node;
        }
        else{   // key.compareTo(node.key) == 0

            // 待删除节点左子树为空的情况
            if(node.left == null){
                Node rightNode = node.right;
                node.right = null;
                size --;
                return rightNode;
            }

            // 待删除节点右子树为空的情况
            if(node.right == null){
                Node leftNode = node.left;
                node.left = null;
                size --;
                return leftNode;
            }

            // 待删除节点左右子树均不为空的情况

            // 找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点
            // 用这个节点顶替待删除节点的位置
            Node successor = minimum(node.right);
            successor.right = removeMin(node.right);
            successor.left = node.left;

            node.left = node.right = null;

            return successor;
        }
    }

    public static void main(String[] args){

        System.out.println("Pride and Prejudice");

        ArrayList<String> words = new ArrayList<>();
        if(FileOperation.readFile("pride-and-prejudice.txt", words)) {
            System.out.println("Total words: " + 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("Total different words: " + map.getSize());
            System.out.println("Frequency of PRIDE: " + map.get("pride"));
            System.out.println("Frequency of PREJUDICE: " + map.get("prejudice"));
        }

        System.out.println();
    }
}

输出:

《玩转数据结构(20)-- 红黑树》

六、 红黑树的性能测试

Main.java

import java.util.ArrayList;

public class Main {

    public static void main(String[] args) {

        System.out.println("Pride and Prejudice");

        ArrayList<String> words = new ArrayList<>();
        if(FileOperation.readFile("pride-and-prejudice.txt", words)) {
            System.out.println("Total words: " + words.size());

            // Collections.sort(words);

            // Test BST
            long startTime = System.nanoTime();

            BST<String, Integer> bst = new BST<>();
            for (String word : words) {
                if (bst.contains(word))
                    bst.set(word, bst.get(word) + 1);
                else
                    bst.add(word, 1);
            }

            for(String word: words)
                bst.contains(word);

            long endTime = System.nanoTime();

            double time = (endTime - startTime) / 1000000000.0;
            System.out.println("BST: " + time + " s");


            // Test AVL
            startTime = System.nanoTime();

            AVLTree<String, Integer> avl = new AVLTree<>();
            for (String word : words) {
                if (avl.contains(word))
                    avl.set(word, avl.get(word) + 1);
                else
                    avl.add(word, 1);
            }

            for(String word: words)
                avl.contains(word);

            endTime = System.nanoTime();

            time = (endTime - startTime) / 1000000000.0;
            System.out.println("AVL: " + time + " s");


            // Test RBTree
            startTime = System.nanoTime();

            RBTree<String, Integer> rbt = new RBTree<>();
            for (String word : words) {
                if (rbt.contains(word))
                    rbt.set(word, rbt.get(word) + 1);
                else
                    rbt.add(word, 1);
            }

            for(String word: words)
                rbt.contains(word);

            endTime = System.nanoTime();

            time = (endTime - startTime) / 1000000000.0;
            System.out.println("RBTree: " + time + " s");
        }

        System.out.println();
    }
}

输出:红黑树性能不占优:大多数操作在查询,红黑树擅长 添加/删除【相对于 AVL树】;

《玩转数据结构(20)-- 红黑树》

只测试添加操作:Main2.java

import java.util.ArrayList;
import java.util.Random;

public class Main2 {

    public static void main(String[] args) {

        // int n = 20000000;
        int n = 20000000;

        Random random = new Random(n);
        ArrayList<Integer> testData = new ArrayList<>(n);
        for(int i = 0 ; i < n ; i ++)
            testData.add(random.nextInt(Integer.MAX_VALUE));

        // Test BST
        long startTime = System.nanoTime();

        BST<Integer, Integer> bst = new BST<>();
        for (Integer x: testData)
            bst.add(x, null);

        long endTime = System.nanoTime();

        double time = (endTime - startTime) / 1000000000.0;
        System.out.println("BST: " + time + " s");


        // Test AVL
        startTime = System.nanoTime();

        AVLTree<Integer, Integer> avl = new AVLTree<>();
        for (Integer x: testData)
            avl.add(x, null);

        endTime = System.nanoTime();

        time = (endTime - startTime) / 1000000000.0;
        System.out.println("AVL: " + time + " s");


        // Test RBTree
        startTime = System.nanoTime();

        RBTree<Integer, Integer> rbt = new RBTree<>();
        for (Integer x: testData)
            rbt.add(x, null);

        endTime = System.nanoTime();

        time = (endTime - startTime) / 1000000000.0;
        System.out.println("RBTree: " + time + " s");
    }
}

输出:【随机】添加/删除 操作中,红黑树 比 AVL 有优势

《玩转数据结构(20)-- 红黑树》

红黑树性能总结:

《玩转数据结构(20)-- 红黑树》

    原文作者:算法小白
    原文地址: https://blog.csdn.net/jianghao233/article/details/82903104
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞