数据结构-红黑树和2-3树

一. 红黑树的定义

  • 1.每个节点或者是红色的,或者是黑色的
  • 2.根节点是黑色的(一棵空树也是红黑树)。
  • 3.每个叶子节点(最后的空节点)是黑色的,定义空节点都是黑色的。
  • 4.如果一个节点是红色的,那么他的孩子节点都是黑色的。
  • 5.从任意一个节点到叶子节点,经过的黑色节点是一样的。

学习红黑树之前,必须先学2-3树

二. 2-3树

学习2-3树,不仅对于理解红黑树有帮助,对于B树的数据结构也有帮助。

  • 2-3 树满足二分搜索树的基本性质,2-3树不是二叉树。
  • 节点可以存放一个元素活着2个元素。
  • 2-3树是一颗绝对平衡的树:任何一个节点左右子树的高度一定是相同的。
    在这里插入图片描述

如何维护2-3的平衡

j

三. 红黑树和2-3树的等价性

在这里插入图片描述
在这里插入图片描述
红黑树是保持“黑”平衡的二叉树,严格意义上,不是平衡二叉树,最大高度为2logN O(logN)
AVL的高度logN. 比AVL树的高度高,查询的时候比AVL慢一点. 如果只查询的时候,AVL快一点。

五. 红黑树添加新元素

2-3树中添加一个新元素: 或者添加晋2-节点,形成3-节点,或者添加进3-节点,暂时形成一个4-节点。
永远添加红色节点。然后维护红黑树。
添加节点之后,保持最终根节点的颜色为黑色。

红黑树的左旋转 、右旋转、颜色翻转

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

红黑树的添加

在这里插入图片描述

总结的流程如下:维护的时机和AVL树一样,添加节点后回溯向上维护。
在这里插入图片描述

六. 红黑树的Java简单实现

package com.mk.coffee.test.dataStructure.redblackTree;

/**
 * 红黑树 《算法导论》:
 * <p>
 * 1.每个节点或者是红色的,或者是黑色的
 * 2.根节点是黑色的(一棵空树也是红黑树)。
 * 3.每个叶子节点(最后的空节点)是黑色的,定义空节点都是黑色的。
 * 4.如果一个节点是红色的,那么他的孩子节点都是黑色的。连接的可能2/3节点,都是黑色的。
 * 5.从任意一个节点到叶子节点,经过的黑色节点是一样的。(2-3树绝对平衡,经过的节点个数相同)
 * 黑色节点的右孩子节点一定是黑色的。
 * <p>
 * 《算法4》 红黑树的发明人 Robert Sedgewick  是 Donald Knuth的学生。
 * <p>
 * 红黑树与 2-3 树的等价性
 * 理解2-3树和红黑树之间的关系。
 * 红色节点都是左孩子
 * 红黑树是保持"黑平衡"的二叉树,严格意义来说不是平衡二叉树。
 * 节点数 为N,最大高度2logN,O(logN)
 * 红黑树比AVL 查找慢一点 ,红黑树2logN, AVLlogN
 * <p>
 * 添加的情况下,RBT好   AVL 28S  RBT20S
 * 查询的情况下:AVL好
 * <p>
 * Java java.util.中的 TreeMap 和 TreeSet 基于红黑树
 * 红黑树的其他实现。
 * 总结:
 * 对于完全随机的数据,普通的二分搜索树就很好用!
 * 确定:极端的情况退化成链表(或者高度不平衡)
 * 对于查询较多的情况下,AVL树很好用!
 * 红黑树牺牲了平衡性(2logN的高度)
 * 统计性能更优(综合增删改查所有的操作,平均性能,红黑树性能最高)
 * <p>
 * <p>
 * 更多的问提
 * <p>
 * <p>
 * 伸展树Splay Tree:局部性原理: 刚被访问的内容下次高概率被再次访问。
 *
 * @author makui
 * @date 2020/03/26
 */
public class RedBlackTree<K extends Comparable<K>, V> {
    //RED BLACk 代表红黑色
    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;
            this.left = null;
            this.right = null;
            //默认是红色 等价2-3树添加节点永远是先和节点融合,然后在做操作
            color = RED;
        }
    }

    private Node root;

    private int size;

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

    //传入的Node左旋转 返回旋转后的根节点
    //左旋转
    // T1 < y < T2 < x < T3 < z < T4
    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;
    }

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

    /**
     * 向红黑树添加新的元素(key,value)
     * 保持根节点的颜色为黑色
     *
     * @param key
     * @param value
     */
    public void add(K key, V value) {
        root = add(root, key, value);
        root.color = BLACK;
    }

    /**
     * 向node为根的红黑色中添加元素(key,value),递归算法
     * 返回插入新节点后红黑树的根
     *
     * @param node
     * @param key
     * @param value
     * @return
     */
    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)) {
            //转换颜色
            flipColor(node);
        }
        return node;
    }

    private void flipColor(Node node) {
        node.color = true;
        node.left.color = false;
        node.right.color = false;

    }

    private boolean isRed(Node node) {
        return node.color;
    }

    /**
     * 返回以node为根节点的二分搜索树中,key所在的节点
     *
     * @param node
     * @param key
     * @return
     */
    private Node getNode(Node node, K key) {
        if (node == null) {
            return null;
        }

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

    public V remove(K key) {
        //todo 仿照BST  自己写
        return null;
    }

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


    /**
     * 查找二分查找树的最小元素
     *
     * @return
     */
    public V minimum() {
        if (size == 0) {
            throw new IllegalArgumentException("BST is empty!");
        }
        return minimum(root).value;
    }

    /**
     * 最小值以node为根的二分搜索树
     *
     * @param node
     * @return
     */
    private Node minimum(Node node) {
        if (node.left == null) {
            return node;
        }
        return minimum(node.left);
    }

    /**
     * 查找二分查找树的最大元素
     *
     * @return
     */
    public V maximum() {
        if (size == 0) {
            throw new IllegalArgumentException("BST is empty!");
        }
        return maximum(root).value;
    }

    /**
     * 最大值以node为根的二分搜索树
     *
     * @param node
     * @return
     */
    private Node maximum(Node node) {
        if (node.right == null) {
            return node;
        }
        return minimum(node.right);
    }


    public int getSize() {
        return size;
    }

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

七. 红黑树和AVL对比

如果插入一个node引起了树的不平衡,AVL和RB-Tree都是最多只需要2次旋转操作,即两者都是O(1);但是在删除node引起树的不平衡时,最坏情况下,AVL需要维护从被删node到root这条路径上所有node的平衡性,因此需要旋转的量级O(logN),而RB-Tree最多只需3次旋转(左旋转,右旋转,颜色翻转),只需要O(1)的复杂度。
其次,AVL的结构相较RB-Tree来说更为平衡,在插入和删除node更容易引起Tree的unbalance,因此在大量数据需要插入或者删除时,AVL需要rebalance的频率会更高。因此,RB-Tree在需要大量插入和删除node的场景下,效率更高。自然,由于AVL高度平衡,因此AVL的search效率更高。
map的实现只是折衷了两者在search、insert以及delete下的效率。总体来说,RB-tree的统计性能是高于AVL的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章