手撕hashmap的红黑树

Posted Code_BinBin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手撕hashmap的红黑树相关的知识,希望对你有一定的参考价值。

之前写了一篇博客,简单的概述了一下什么 是红黑树,现在我们就来实现一下

JDK1.8的HashMap底层红黑树实现

准备工作

  • 新建一个maven项目
  • 新建RBTree类
  • 新建RBTreeTest类
  • 新建TreeOperation类

在这里插入图片描述

首先我们得知道一颗红黑树得写什么方法

  • 判断节点是不是红(isRed)
  • 设节点为红(setRed)
  • 判断节点是不是黑(isBlack)
  • 设节点为黑(setBlack)
  • 获取节点的父亲(parentof)
  • 左旋(leftRotate)
  • 右旋(rightRotate)
  • 插入节点(insert)
  • 修正红黑树(inserFIxUp)
  • 测试红黑树的正确性

RBTree

package com.znb;

import java.util.Stack;

/**
 * @Auther: znb
 * @Description: ①创建RBTree,定义颜色
 * <p>
 * ②创建RBNode
 * <p>
 * ③辅助方法定义:parentOf(node),isRed(node),setRed(node),setBlack(node),inOrderPrint()
 * <p>
 * ④左旋方法定义:leftRotate(node)
 * <p>
 * ⑤右旋方法定义:rightRotate(node)
 * <p>
 * ⑥公开插入接口方法定义:insert(K key, V value);
 * <p>
 * ⑦内部插入接口方法定义:insert(RBNode node);
 * <p>
 * ⑧修正插入导致红黑树失衡的方法定义:insertFIxUp(RBNode node);
 * <p>
 * ⑨测试红黑树正确性
 */

public class RBTree<k extends Comparable<k>, v> {
    private  static  final  boolean RED =true;
    private  static  final  boolean BLACK=false;
    private RBNode root;
    public RBNode getRoot() {
        return root;
    }
    /**
     *
     * @param node
     * @return
     */

    private  RBNode  parentof(RBNode node){
        if(node!=null){
            return  node.parent;
        }else {
            return null;
        }

    }

    /**
     * 判断节点是否为红色
     * @param node
     * @return
     */
    private boolean isRed(RBNode node) {
        if (node != null) {
            return node.color == RED;
        }
        return false;
    }

    /**
     * 节点是否为黑色
     *
     * @param node
     * @return
     */
    private boolean isBlack(RBNode node) {
        if (node != null) {
            return node.color == BLACK;
        }
        return false;
    }

    /**
     * 设置节点为红色
     *
     * @param node
     */
    private void setRed(RBNode node) {
        if (node != null) {
            node.color = RED;
        }
    }

    /**
     * 设置节点为黑色
     *
     * @param node
     */
    private void setBlack(RBNode node) {
        if (node != null) {
            node.color = BLACK;
        }
    }
    /**
     * 中序打印二叉树
     */
    public void inOrderPrint() {
        inOrderPrint(this.root);
    }

    private void inOrderPrint(RBNode root) {
        if (root != null) {
            inOrderPrint(root.left);
            System.out.println("key:" + root.key + ",value:" + root.value);
            inOrderPrint(root.right);
        }
    }
    /**
     * 左旋方法
     * 左旋示意图:左旋x节点
     *   p                   p
     *   |                   |
     *   x                   y
     *  / \\      ---->      / \\
     * lx  y               x   ry
     *    / \\             / \\
     *   ly  ry          lx  ly
     *
     * 左旋做了几件事?
     * 1.将x的右子节点指向y的左子节点(ly),并且把y的左子节点更新为x
     * 2.当x的父节点(不为空时),更新y的父节点为x的父节点,并将x的父节点 指定 子树(当前x的子树位置) 指定为y
     * 3.将x的父节点更新为y,将y的左子节点更新为x
     */
    private void leftRotate(RBNode x) {
        RBNode y = x.right;// 获得y
        // 1.将x的右子节点指向y的左子节点(ly),并且把y的左子节点更新为x
        x.right = y.left;
        if (y.left != null) {
            y.left.parent = x;
        }

        // 2.当x的父节点(不为空时),更新y的父节点为x的父节点,并将x的父节点 指定 子树(当前x的子树位置) 指定为y
        if (x.parent != null) {
            y.parent = x.parent;
            if (x == x.parent.left) {// 如果x是其父节点的左子节点,则将y放在x父节点的左边
                x.parent.left = y;
            } else {
                x.parent.right = y;// 如果x是其父节点的右子节点,则将y放在x父节点的右边
            }
        } else {// 说明x为根节点,此时需要更新y为根节点 的引用
            this.root = y;
            this.root.parent = null;// 根节点无父节点
        }

        // 3.将x的父节点更新为y,将y的左子节点更新为x
        x.parent = y;
        y.left = x;
    }
    /**
     * 右旋方法
     * 右旋示意图:右旋y节点
     *
     *     p                       p
     *     |                       |
     *     y                       x
     *    / \\          ---->      / \\
     *   x   ry                  lx  y
     *  / \\                         / \\
     * lx  ly                      ly  ry
     *
     * 右旋都做了几件事?
     * 1.将y的左子节点指向x的右子节点,并且更新x的右子节点的父节点为y
     * 2.当y的父节点不为空时,更新x的父节点为y的父节点,更新y的父节点的指定子节点(y当前位置) 为x
     * 3.更新y 的父节点为x ,更新x 的右子节点为y
     */
    private void rightRotate(RBNode y) {
        RBNode x = y.left;// 获得 x
        // 1.将x的右子节点 赋值 给了 y 的左子节点,并且更新x的右子节点的父节点为 y
        y.left = x.right;
        if(x.right != null) {
            x.right.parent = y;
        }

        // 2.将y的父节点p(非空时)赋值给x的父节点,同时更新p的子节点为x(左或右)
        if(y.parent != null) {
            x.parent = y.parent;
            if(y.parent.left == y) {// 如果y是其父节点的左子节点,则将x放在y父节点的左边
                y.parent.left = x;
            } else {// 如果y是其父节点的右子节点,则将x放在y父节点的右边
                y.parent.right = x;
            }
        } else {// 说明y为根节点,此时需要更新x为根节点 的引用
            this.root = x;
            this.root.parent = null;// 根节点无父节点
        }

        // 3.将x的右子节点赋值为y,将y的父节点设置为x
        x.right = y;
        y.parent = x;
    }
    /**
     * public插入方法
     *
     * @param key
     * @param value
     */
    public void insert(k key, v value) {
        RBNode node = new RBNode();
        node.setKey(key);
        node.setValue(value);
        // 新节点 一定要是红色!
        node.setColor(RED);

        insert(node);
    }
    private void insert(RBNode node) {
        // 第一步:查找当前要插入节点node的父节点
        RBNode parent = null;// 声明要插入节点node的父节点
        RBNode x = this.root;

        while (x != null) {
            parent = x;

            /**
             * cmp > 0 说明node.key 大于 x.key 需要到x 的右子树查找
             * cmp == 0 说明node.key 等于 x.key 需要进行替换操作
             * cmp < 0 说明node.key 小于 x.key 需要到x 的左子树查找
             */
            int cmp = node.key.compareTo(x.key);
            if (cmp > 0) {
                x = x.right;
            } else if (cmp == 0) {
                x.setValue(node.getValue());
                return;// 修改完后 就不再继续往下面的代码执行了
            } else {
                x = x.left;
            }
        }

        /**
         * 退出上面的while循环后,到这里,说明树中没有相同key 的元素
         *
         * 需要添加新元素node到 x(parent) 目前位置的左子树/右子树
         */
        node.parent = parent;
        if (parent != null) {
            // 判断node与parent 的key 谁大
            int cmp = node.key.compareTo(parent.key);
            if (cmp > 0) {// 当前node的key比parent 的key大,需要把node放入parent 的右子节点
                parent.right = node;
            } else {// 当前node的key比parent 的key小,需要把node放入parent 的左子节点
                parent.left = node;
            }
        } else {// parent == null; 说明为空树
            this.root = node;// 直接给树根赋值为node
        }

        // 新元素node 加入树中之后,要调用修复红黑树平衡的方法
        insertFixUp(node);

    }
    
    private void insertFixUp(RBNode node) {
        RBNode parent = parentof(node);// 当前节点的父节点
        RBNode gparent = parentof(parent);// 当前节点的爷爷节点
        // 存在父节点且父节点为红色
        if (parent != null && isRed(parent)) {
            // 父节点是红色的,那么一定存在爷爷节点(性质2:根节点只能是黑色)

            // 父节点为爷爷节点的左子树
            if (parent == gparent.left) {
                RBNode uncle = gparent.right;
                // 情景4.1:叔叔节点存在,并且为红色(父-叔 双红)
                // 将父和叔染色为黑色,再将爷爷染红,并将爷爷设置为当前节点,进入下一次循环判断
                if (uncle != null && isRed(uncle)) {
                    setBlack(parent);
                    setBlack(uncle);
                    setRed(gparent);
                    insertFixUp(gparent);
                    return;
                }

                // 情景4.2:叔叔节点不存在,或者为黑色,父节点为爷爷节点的左子树
                if (uncle == null || isBlack(uncle)) {
                    /**
                     * 情景4.2.1:插入节点为其父节点的左子节点(LL情况)
                     * 处理:将父节点染为黑色,将爷爷染为红色,然后以爷爷节点右旋即可
                     */
                    // 插入节点为其父节点的左子节点(LL情况)=>
                    // 变色(父节点变黑,爷爷节点变红),右旋爷爷节点
                    if (node == parent.left) {
                        setBlack(parent);
                        setRed(gparent);
                        rightRotate(gparent);// 以gparent 右旋
                    }

                    /**
                     * 情景4.2.2:插入节点为其父节点的右子节点(LR情况)
                     * 处理:将父节点进行一次左旋,得到LL双红情景(4.2.1),然后指定父节点为当前节点进行下一轮处理
                     */
                    // 插入节点为其父节点的右子节点(LR情况)=>
                    // 左旋(父节点),当前节点设置为父节点,进入下一次循环
                    if (node == parent.right) {
                        leftRotate(parent);// parent 左旋
                        insertFixUp(parent);// 进行下一轮处理
                        return;
                    }
                }
            } else {// 父节点为爷爷节点的右子树
                RBNode uncle = gparent.left;
                // 情景4.1:叔叔节点存在,并且为红色(父-叔 双红)
                // 将父和叔染色为黑色,再将爷爷染红,并将爷爷设置为当前节点,进入下一次循环判断
                if (uncle != null && isRed(uncle)) {
                    setBlack(parent);
                    setBlack(uncle);
                    setRed(gparent);
                    insertFixUp(gparent);// 进行下一轮处理
                    return;
                }

                // 情景4.3:叔叔节点不存在,或者为黑色,父节点为爷爷节点的右子树
                if (uncle == null || isBlack(uncle)) {
                    /**
                     * 情景4.3.1:插入节点为其父节点的右子节点(RR情况)
                     * 处理:将父节点染为黑色,将爷爷节点染为红色,然后以爷爷节点左旋即可
                     */
                    // 插入节点为其父节点的右子节点(RR情况)=>
                    // 变色(父节点变黑,爷爷节点变红),右旋爷爷节点
                    if (node == parent.right) {
                        setBlack(parent);
                        setRed(gparent);
                        leftRotate(gparent);
                    }

                    /**
                     * 情景4.3.2:插入节点为其父节点的左子节点(RL情况)
                     * 处理:以父节点进行一次右旋,得到RR双红情景(4.3.1),然后指定父节点为当前节点进行下一轮处理
                     */
                    // 插入节点为其父节点的左子节点(RL情况)
                    // 右旋(父节点)得到RR情况,当前节点设置为父节点,进入下一次循环
                    if (node == parent.left) {
                        rightRotate(parent);
                        insertFixUp(parent);
                        return;
                    }
                }
            }
        }
        setBlack(this.root);
    }

    static class RBNode<k extends  Comparable<k>,v>{
        private RBNode parent;
        private RBNode left;
        private RBNode right;
        private boolean color;
        private k key;
        private  v value;

        public RBNode() {
        }

        public RBNode(RBNode parent, RBNode left, RBNode right, boolean color, k key, v value) {
            this.parent = parent;
            this.left = left;
            this.right = right;
            this.color = color;
            this.key = key;
            this.value = value;
        }

        public RBNode getParent() {
            return parent;
        }
        public void setParent(RBNode parent) {
            this.parent = parent;
        }
        public RBNode getLeft() {
            return left;
        }
        public void setLeft(RBNode left) {
            this.left = left;
        }
        public RBNode getRight() {
            return right;
        }
        public void setRight(RBNode right) {
            this.right = right;
        }
        public boolean isColor() {
            return color;
        }
        public void setColor(boolean color) {
            this.color = color;
        }
        public k getKey() {
            return key;
        }
        public void setKey(k key) {
            this.key = key;
        }
        public v getValue() {
            return value;
        }
        public void setValue(v value) {
            this.value = value;
        }
    }
}

RBTreeTest

package com.znb;

import java.util.Scanner;

/**
 * @Auther: znb
 * @Description: RBTree红黑树 测试
 */
public class RBTreeTest {

    public static void main(String[] args) {
        RBTree<String, Object> rbtree = new RBTree();
        //测试输入:ijkgefhdabc
        while(true) {
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入key:");
            String key = sc.next();

            rbtree.insert(key, null);
            TreeOperation.show(rbtree.getRoot());
        }
    }
}

测试红黑树的工具(直接cv)

package com.znb;

/**
 * @Auther: znb
 * @Description: 打印红黑树的工具类
 */
public class TreeOperation {
    /*
           树的结构示例:
              1
            /   \\
          2       3
         / \\     / \\
        4   5   6   7
    */

    // 用于获得树的层数
    public static int getTreeDepth(RBTree.RBNode root) {
        return root == null ? 0 : (1 + Math.max(getTreeDepth(root.getLeft()), getTreeDepth(root.getRight())));
    }


    private static void writeArray(RBTree.RBNode currNode, int rowIndex, int columnIndex, String[][] res, int treeDepth) {
        // 保证输入的树不为空
        if (currNode == null) return;
        // 先将当前节点保存到二维数组中
        res[rowIndex][columnIndex] = String.valueOf(currNode.getKey() + "-" + (currNode.isColor() ? "R" : "B") + "");

        // 计算当前位于树的第几层
        int currLevel = ((rowIndex + 1) / 2);
        // 若到了最后一层,则返回
        if (currLevel == treeDepth) return;
        // 计算当前行到下一行,每个元素之间的间隔(下一行的列索引与当前元素的列索引之间的间隔)
        int gap = treeDepth - currLevel - 1;

        // 对左儿子进行判断,若有左儿子,则记录相应的"/"与左儿子的值
        if (currNode.getLeft() != null) {
            res[rowIndex + 1][columnIndex - gap] = "/";
            writeArray(currNode.getLeft(), rowIndex + 2, columnIndex - gap * 2, res, treeDepth);
        }

        // 对右儿子进行判断,若有右儿子,则记录相应的"\\"与右儿子的值
        if (currNode.getRight() != null) {
            res[rowIndex + 1][columnIndex + gap] = "\\\\";
            writeArray(currNode.getRight(), rowIndex + 2, columnIndex + gap * 2, res, treeDepth);
        }
    }


    public static void show(RBTree.RBNode root一文看懂 HashMap 中的红黑树实现原理

C++进阶 | 手撕红黑树

算法手撕红黑树(下)—— 一张流程图梳理删除操作(含实现代码)

JAVA集合:TreeMap红黑树深度解析

JDK源码那些事儿之HashMap.TreeNode

(多图)那些年,面试被虐过的红黑树