Phase2 Day14 Collections & RedBlackTree

1 Collections

  • 此类完全由在 collection 上进行操作或返回 collection 的静态方法组成。它包含在 collection 上操作的多态算法,即“包装器”,包装器返回由指定 collection 支持的新 collection,以及少数其他内容
  • 如果为此类的方法所提供的 collection 或类对象为 null,则这些方法都将抛出 NullPointerException
API:
    排序:
        static <T extends Comparable<? super T>> void sort(List<T> list)
        根据元素的自然顺序 对指定列表按升序进行排序
        static <T> void sort(List<T> list, Comparator<? super T> c)
		根据指定比较器产生的顺序对指定列表进行排序

    逆序:
        static void reverse(List<?> list)
        反转指定列表中元素的顺序
        
    乱序:
        static void shuffle(List<?> list)
        使用指定的随机源对指定列表进行置换
        
    二分查找:
        static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key)
        使用二分搜索法搜索指定列表,以获得指定对象
        static <T> int binarySearch(List<? extends T> list, T key, Comparator<? super T> c)
        使用二分搜索法搜索指定列表,以获得指定对象
        
    旋转:
        static void rotate(List<?> list, int distance)
        根据指定的距离轮换指定列表中的元素
        
    交换:
        static void swap(List<?> list, int i, int j)
        在指定列表的指定位置处交换元素
        
    求最值:
        static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)
        根据元素的自然顺序,返回给定 collection 的最大元素
        static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp)
        根据指定比较器产生的顺序,返回给定 collection 的最大元素
        static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll)
        static <T> T min(Collection<? extends T> coll, Comparator<? super T> comp)
        
    互斥:
        static boolean disjoint(Collection<?> c1, Collection<?> c2)
        如果两个指定 collection 中没有相同的元素,则返回 true
        
    视图:
        同步视图
        不可修改视图
        类型安全视图
 */

2 234树

2.1 概述

  • 在普通的二叉查找树上进行了扩展,它允许有多个键(1~3个)
  • 树保持完美平衡(根到每个叶子结点的路径都是一样长)
  • 每个结点可以拥有1, 2, 或者3个键
    2-node:1个键,两个孩子
    3-node:2个键,三个孩子
    4-node:3个键,四个孩子
    在这里插入图片描述

2.2 查找

  • 和当前结点的所有的键进行比较
  • 如果当前结点中没有,就找到对应的区间
  • 依据链接找到下一个结点 (递归)
    在这里插入图片描述

2.3 插入

情景1:2-node: 转换成3-node
List item
情景2:3-node: 转换成4node
在这里插入图片描述
情景3:4-node: 节点分裂

在这里插入图片描述
在遍历时提供了两种分裂4-node的思路

  • Bottom-up 自底向上
  • Top-down 自顶向下

Top - down

  • Case 1:根结点是 4-node
    在这里插入图片描述
  • Case 2: 父节点是 2 -node
    在这里插入图片描述
  • Case 3: 父节点是 3-node
    在这里插入图片描述
    结果
  • 不变式:当前节点不是4-node
  • 4-node 的父亲不是 4-node
  • 到达的叶子结点要么是2-node,要么是3-node
  • 注:这些变换都是局部变换,不会影响到无关的其他结点

2.4 性能分析

  • 是一颗完美平衡树
  • 最坏情况: lg N [全部是2-node]
  • 最好情况: log4N = ½ lg N  [全部是4-node]
  • 100万个结点高度在[10, 20]
  • 10亿个结点高度在[15, 30]
  • 2-3-4树保证了树的高度为 O(lgN)

3 RedBlackTree

3.1 概述

  • 用 BST 来表现 2-3-4 树
  • 用”内部的”红色边来表示 3-node 和 4-node
    在这里插入图片描述
  • 3-node 的红色边是左倾的
    在这里插入图片描述
  • 2-3-4 树能够被表示成 BST,它们之间有一种对应关系!但是它们这种对应不是一对一的 在这里插入图片描述
  • 算法导论中关于红黑树的定义:
  1. 一棵红黑树是满足下面红黑性质的二叉搜索树: 每个结点或者是红色的,或者是黑色的
  2. 根结点是黑色的
  3. 叶结点 (Nil) 是黑色的
  4. 如果一个结点是红色的,则它的两个子结点都是黑色的 (4-node 只有一种编码方式)
  5. 对每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。(黑高平衡, 2-3-4树是一个完美平衡的树)

3.2 实现

3.2.1 构造方法
public class RedBlackTree<K extends Comparable<? super K>, V> {
    private static final boolean RED = true;
    private static final boolean BLACK = false;
    // 属性
    private TreeNode root;

    private class TreeNode {
        K key;
        V value;
        TreeNode left;
        TreeNode right;
        boolean color;
        int size; // 树的结点个数 size = 左子代的个数 + 右子代的个数 + 1

        public TreeNode(K key, V value, boolean color, int size) {
            this.key = key;
            this.value = value;
            this.color = color;
            this.size = size;
        }
    }
}
3.2.2 Helper
  • ★树的局部变换——旋转
  • 在LLRB中,我们需要通过左旋和右旋操作来保证LLRB的性质
    在这里插入图片描述
//避免空指针异常
    private boolean isRed(TreeNode x) {
        if (x == null) return false;
        else return x.color;
    }

    //避免空指针异常
    private int size(TreeNode x) {
        if (x == null) return 0;
        else return x.size;
    }

    private TreeNode rotateLeft(TreeNode h) {
        TreeNode x = h.right;
        h.right = x.left;
        x.left = h;
        x.color = h.color;
        h.color = RED;
        x.size = h.size;
        h.size = size(h.left) + size(h.right) + 1; 
        return x;
    }

    private TreeNode rotateRight(TreeNode h) {
        TreeNode x = h.left;
        h.left = x.right;
        x.right = h;
        x.color = h.color;
        h.color = RED;
        x.size = h.size;
        h.size = size(h.left) + size(h.right) + 1;
        return x;
    }

    private void flipColors(TreeNode h) {
        h.color = !h.color;
        h.left.color = !h.left.color;
        h.right.color = !h.right.color;
    }

    private TreeNode moveRedRight(TreeNode h) {
        flipColors(h);
        if (isRed(h.left.left)) {
            h = rotateRight(h);
            flipColors(h);
        }
        return h;
    }

    private TreeNode moveRedLeft(TreeNode h) {
        flipColors(h);
        if (isRed(h.right.left)) {
            h.right = rotateRight(h.right);
            h = rotateLeft(h);
            flipColors(h);
        }
        return h;
    }

    private TreeNode fixUp(TreeNode x) {
        if (isRed(x.right)) x = rotateLeft(x);
        if (isRed(x.left) && isRed(x.left.left)) x = rotateRight(x);
        if (isRed(x.left) && isRed(x.right)) flipColors(x);
        x.size = size(x.left) + size(x.right) + 1;
        return x;
    }
3.2.3 ★插入—Put方法
  • 插入步骤
  • 自顶向下,沿查找路径分解4-node
  • 在底端添加新结点
  • 自底向上,通过旋转操作来更正非法的 3-node 和 4-node (分解4-node 和添加新结点都可能产生)
/**
     * 添加键值对, 如果键已经存在,则更新键关联的值
     * @param key 键
     * @param value 值
     */
    public void put(K key, V value) {
        if (key == null || value == null) {
            throw new IllegalArgumentException("Key or value cannot be null");
        }
        root = put(root, key, value);
        root.color = BLACK;
        check();
    }

    private TreeNode put(TreeNode x, K key, V value) {
        // 在底部插入新结点 (边界条件)
        if (x == null) return new TreeNode(key, value, RED, 1);
        // 比较过程
        int cmp = key.compareTo(x.key);
        if (cmp < 0) x.left = put(x.left, key, value);
        else if (cmp > 0) x.right = put(x.right, key, value);
        else x.value = value;
        // 自底向上沿着查找路径修复非法的3-node和4-node
        return fixUp(x);
    }

插入节时父节点要么是 2-node, 要么是 3-node,总共有 2 + 3 = 5 种情况
在这里插入图片描述

  • 插入新结点,会造成右倾的3-node,和连续的红链接 (不规范的4-node)。我们需要通过旋转操作来更正这些链接

  • ★ 分裂4-node(反转操作)
    在这里插入图片描述

	private void flipColors(TreeNode h) {
        h.color = !h.color;
        h.left.color = !h.left.color;
        h.right.color = !h.right.color;
    }
  • 这也是一个局部变换
  • 保持了黑高的平衡
  • 将红色链接传递给了父结点
  • 相当于在父结点中插入一个新的结点
  • 在父结点种插入新结点的情形和在底部插入新结点的情形是一模一样的

最后,更正非法的 3-node 和 4-node 有三种情况,我们统一成两个步骤
在这里插入图片描述

	if (isRed(x.right)) x = rotateLeft(x);
    if (isRed(x.left) && isRed(x.left.left)) x = rotateRight(x);

注:若反转操作在前会造成底部依旧可能存在4-node,故反转操作应该在最后

	private TreeNode fixUp(TreeNode x) {
        if (isRed(x.right)) x = rotateLeft(x);
        if (isRed(x.left) && isRed(x.left.left)) x = rotateRight(x);
        if (isRed(x.left) && isRed(x.right)) flipColors(x);
        x.size = size(x.left) + size(x.right) + 1;
        return x;
    }
3.2.4 删除最大值

思路:

  • 沿着最右边的分支向下查找

  • 如果最大结点是3-node, 或者4-node——直接删除 (不影响2-3-4树的完美平衡)
    在这里插入图片描述

  • 如果最大节点是2-node?
    删除最大值算法思路:确保当前结点的右孩子 (如果有) 不是2-node
    在这里插入图片描述

  • 对于根节点:

  • 根节点不是2-node与前面分析的一样

  • 根结点是2-node,有在这里插入图片描述

综上,对于 2-3 树而言,删除最大值的策略

  • 不变式: 保证当前结点不是2-node
  • 必要的时候,我们可以引入4-node
  • 在最底端删除最大值
  • 沿查找路径自底向上fixUp()

实现

  • 遇到左倾的红色链接,右旋
  • 不变式:h.isRed() || h.right.isRed() (当前结点不是2-node)
  • 在最底端删除

Case 1:遍历时遇到左倾的3-node
在这里插入图片描述
Case 2:如果右孩子是2-node, 我们需要从它的兄弟结借结点。这有两种情况
  Case2.1:左兄弟是 2-node (h.left.left is BLACK)在这里插入图片描述
  Case2.2:左兄弟是 3-node (h.left.left is RED)
  (下图时已经先对父节点进行了反转操作)在这里插入图片描述

  • 完成这些步骤之后,我们就可以在底端删除最大结点了。但是,我们发现这些局部变换会引入红色的右链接和4-node。我们需要自底向上修复这些不规范的结点
	private TreeNode moveRedRight(TreeNode h) {
        flipColors(h);
        if (isRed(h.left.left)) {
            h = rotateRight(h);
            flipColors(h);
        }
        return h;
    }
/**
     * 删除红黑树的最大结点
     */
    public void deleteMax() {
        if (root == null) throw new NoSuchElementException("Tree is Empty");
        // 如果根结点是2-node, 就把根结点的颜色变成红色
        if (!isRed(root.left)) root.color = RED;
        root = deleteMax(root);
        if (root != null) root.color = BLACK;
        check();
    }

    private TreeNode deleteMax(TreeNode x) {
        // 右旋左倾的红色链接
        if (isRed(x.left)) x = rotateRight(x);
        // 在底部删除最大结点
        if (x.right == null) return null;
        // 如果右孩子是2-node, 就要从左孩子借结点
        if (!isRed(x.right) && !isRed(x.right.left)) {
            x = moveRedRight(x);
        }
        // 往右走
        x.right = deleteMax(x.right);
        // 自底向上修复
        return fixUp(x);
    }

在这里插入图片描述

3.2.5 删除最小值

删除最小值的策略和删除最大值的策略是一致的,只是有些许不同

  • 不变式: 保证当前结点不是2-node(h.isRed() || h.left.isRed())
  • 必要的时候,我们可以引入4-node
  • 在最底端删除最小值
  • 沿查找路径自底向上fixUp()

因为我们讨论的是 2-3 树模型,因此是没有红色右链接的,故不需要左旋
   如果左孩子是2-node (!h.left.isRed() && !h.left.left.isRed()),此时 我们需要从兄弟中借结点,这分两种情况

  • Case 1: 右孩子是 2-node (h.right.left is BLACK)在这里插入图片描述

  • Case 2: 右孩子是 3-node (h.right.left is RED)在这里插入图片描述

	private TreeNode moveRedLeft(TreeNode h) {
        flipColors(h);
        if (isRed(h.right.left)) {
            h.right = rotateRight(h.right);
            h = rotateLeft(h);
            flipColors(h);
        }
        return h;
    }
	public void deleteMin() {
        if (root == null) throw new NoSuchElementException("Tree is Empty!");
        if (!isRed(root.left)) root.color = RED;
        root = deleteMin(root);
        if (root != null) root.color = BLACK;
        check();
    }

    private TreeNode deleteMin(TreeNode x) {
        if (x.left == null) return null;
        // 如果左孩子是2-node, 那么就从右孩子中借结点过去
        if (!isRed(x.left) && !isRed(x.left.left)) {
            x = moveRedLeft(x);
        }
        // 往左走
        x.left = deleteMin(x.left);
        return fixUp(x);
    }
3.2.6 删除—delete

删除算法的要点:当前结点或者它的孩子结点中的某一个是 RED.

  • 查询路径往左: 左孩子不是2-node,moveRedLeft()
  • 查询路径往右: 右孩子不是2-node,moveRedRight()
  • 在底端删除结点
  • fixup()
	/**
     * 删除红黑树中键与key相等的检点
     * @param key 给定的键
     */
    public void delete(K key) {
        if (key == null) throw new IllegalArgumentException("Key cannot be null");
        if (root == null) throw new NoSuchElementException("Tree is Empty");
        if (!contains(key)) return ;
        if (!isRed(root.left)) root.color = RED;
        root = delete(root, key);
        if (root != null) root.color = BLACK;
        check();
    }

    private TreeNode delete(TreeNode x, K key) {
        if (key.compareTo(x.key) < 0) {
            // 如果左孩子是2-node, 从右孩子中借结点
            if (!isRed(x.left) && !isRed(x.left.left)) {
                x = moveRedLeft(x);
            }
            // 往左走
            x.left = delete(x.left, key);
        } else {
            // 把左倾的红色链接右旋
            if (isRed(x.left)) x = rotateRight(x);
            // 删除的结点是底部结点
            if (key.compareTo(x.key) == 0 && x.right == null) return null;
            // 如果右孩子是2-node, 从左孩子中借结点
            if (!isRed(x.right) && !isRed(x.right.left)) {
                x = moveRedRight(x);
            }
            if (key.compareTo(x.key) == 0) {
                TreeNode minOfRight = min(x.right);
                x.key = minOfRight.key;
                x.value = minOfRight.value;
                // 在右子树中删除最小结点
                x.right = deleteMin(x.right);
            } else x.right = delete(x.right, key);
        }
        // 沿着查找路径自底向上修复
        return fixUp(x);
    }
3.2 无序符号表
无序符号表:
    void put(K key, V value)
    V get(K key)
    void delete(K key)
    void clear()
    boolean contains(K key)
    boolean isEmpty()
    int size()
    Set<K> keys()

/**
     * 查找key关联的value
     *
     * @param key 键
     * @return 键关联的值, 如果key不存在返回null
     */
    public V get(K key) {
        if (key == null) {
            throw new IllegalArgumentException("Key cannot be null");
        }
        TreeNode x = root;
        while (x != null) {
            int cmp = key.compareTo(x.key);
            if (cmp < 0) x = x.left;
            else if (cmp > 0) x = x.right;
            else return x.value;
        }
        return null;
    }

    /**
     * 判断key是否在红黑树中存在
     *
     * @param key 键
     * @return 如果存在返回true, 否则返回false
     */
    public boolean contains(K key) {
        return get(key) != null;
    }
    
     /**
     * 获取红黑树中键值对的个数
     * @return 键值对的个数
     */
    public int size() {
        return size(root);
    }

    /**
     * 清空红黑树中所有的元素
     */
    public void clear() {
        root = null;
    }

    /**
     * 判断红黑树是否为空
     * @return 如果红黑树为空返回true, 否则返回false
     */
    public boolean isEmpty() {
        return root == null;
    }
3.2. 有序符号表
有序符号表:
    K min()
    K max()
    K floor(K key)
    K ceiling(K key)
    int rank(K key)
    K select(int k)
    void deleteMin()
    void deleteMax()
    int size(K low, K high)
    Set<K> keys(K low, K high)

	 /**
     * 获取红黑树中的最小的键
     * @return 最小的键
     */
    public K min() {
        if (root == null) throw new NoSuchElementException("Tree is Empty!");
        return min(root).key;
    }

    private TreeNode min(TreeNode x) {
        while (x.left != null) x = x.left;
        return x;
    }

    /**
     * 获取红黑树中的最大的键
     * @return 最大的键
     */
    public K max() {
        if (root == null) throw new NoSuchElementException("Tree is Empty!");
        return max(root).key;
    }

    private TreeNode max(TreeNode x) {
        while (x.right != null) x = x.right;
        return x;
    }
    
	/**
     * 获取红黑树中小于等于给定key的最大键
     * @param key 给定的key
     * @return 小于等于给定key的最大键
     */
    public K floor(K key) {
        if (key == null) throw new IllegalArgumentException("Key cannot be null");
        if (root == null) throw new NoSuchElementException("Tree is Empty!");
        TreeNode x = floor(root, key);
        if (x == null) return null;
        else return x.key;
    }

    private TreeNode floor(TreeNode x, K key) {
        // 边界条件
        if (x == null) return null;
        int cmp = key.compareTo(x.key);
        if (cmp == 0) return x;
        // 在左子树中查找
        if (cmp < 0) return floor(x.left, key);
        // 那么当前结点就是一个备选方案, 可能右子树中存在小于key的键
        TreeNode t = floor(x.right, key);
        if (t != null) return t;
        return x;
    }

    /**
     * 获取红黑树中大于等于给定key的最小键
     * @param key 给定key
     * @return 大于等于给定key的最小键
     */
    public K ceiling(K key) {
        if (key == null) throw new IllegalArgumentException("Key cannot be null");
        if (root == null) throw new NoSuchElementException("Tree is Empty!");
        TreeNode x = ceiling(root, key);
        if (x == null) return null;
        else return x.key;
    }

    private TreeNode ceiling(TreeNode x, K key) {
        if (x == null) return null;
        int cmp = key.compareTo(x.key);
        if (cmp == 0) return x;
        if (cmp > 0) return ceiling(x.right, key);
        // 当前结点就是备选方案, 可能它的左子树中存在大于key的结点
        TreeNode t = ceiling(x.left, key);
        if (t != null) return t;
        return x;
    }
3.2. Check
private boolean check() {
        boolean is23 = is23();
        if (!is23) System.err.println("The tree is not 23!");
        boolean isBalanced = isBalanced();
        if (!isBalanced) System.err.println("The tree is not balanced!");
        return is23 && isBalanced;
    }

    private boolean is23() {
        return !isRed(root) && is23(root);
    }

    private boolean is23(TreeNode x) {
        if (x == null) return true;
        if (isRed(x.right)) return false;
        if (isRed(x.left) && isRed(x.left.left)) return false;
        return is23(x.left) && is23(x.right);
    }

    private boolean isBalanced() {
        // 求出根结点到某个叶子结点的黑高 black
        int black = 0;
        TreeNode x = root;
        while (x != null) {
            if (!isRed(x)) black++;
            x = x.left;
        }
        // 搜索从根结点到叶子结点的任意一条路径,判断它们的黑高是否等于black
        return isBalanced(root, black);
    }

    private boolean isBalanced(TreeNode x, int black) {
        if (x == null) return black == 0;
        if (!isRed(x)) black--;
        return isBalanced(x.left, black) && isBalanced(x.right, black);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值