手写HashMap中的红黑树


前言

java8的HashMap中,使用了红黑树,本文主要是通过手写红黑树插入和查找代码来理解其特性和作用。


一、红黑树是什么?

红黑树是一种数据结构,如果学过数据结构的同学,应该会比较了解,红黑树是一种平衡二叉树,是有234树转变而来。没学过的同学,只需要知道以下两点:

  • 作用:有序且规则的结构,在HashMap中主要是方便查找元素
  • 消耗:插入和删除的消耗相对较少(比平衡二叉树少,比链表多),查找的消耗相对较少(比链表少,比平衡二叉树多)。是一种折中或综合性的策略。

二、代码实现

1.构建存放键值对的节点类

  /**
  * 需要一个Node类来存放KV,且该Node可以指向下个Node(链表结构)
  * 这里直接实现了Map.Entry接口,也可以自己手动写一个Entry接口
  * 
  * @param <K> 键
  * @param <V> 值
  */
  class Node<K, V> implements Map.Entry<K, V>{
    Node<K, V> next;
    final K key;
    V value;
    int hash;

    public Node(Node<K, V> next, K key, V value, int hash) {
      this.next = next;
      this.key = key;
      this.value = value;
      this.hash = hash;
    }

    @Override
    public K getKey() {
      return this.key;
    }

    @Override
    public V getValue() {
      return this.value;
    }

    @Override
    public V setValue(V value) {
      return this.value=value;
    }

    @Override
    public boolean equals(Object obj) {
      return (obj instanceof Node) && 
      ((Node)obj).key.equals(this.key) && 
      ((Node)obj).value.equals(this.value);
    }
  }

2.构建树节点类

继承Node,创建红黑树节点。代码如下(示例):

/**
   * 红黑树节点:
   * 有颜色red
   * 有左右叶子结点 left、right
   * 有父节点 superior
   * @param <K>
   * @param <V>
   */
  class TreeNode<K,V> extends Node<K,V>{
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> superior;
    boolean red;

    public TreeNode(Node<K, V> node){super(node.next,node.key,node.value,node.hash);}
  }

3. 插入方法

/**
   * 该方法包含递归,从根节点开始,每次比对大小进行分路(左右),有空位即临时插入。
   * 然后进行红黑树平衡操作
   *
   * @param superior 需要比较大小父节点
   * @param newTNode 新插入的节点
   * @return 返回平衡操作后的节点
   */
  private TreeNode<K, V> insertTreeNode(TreeNode<K,V> superior, TreeNode<K,V> newTNode) {
    newTNode.red = true;
    boolean left;//插入方向
    if(compareTreeNode(superior.key,newTNode.key)){
      logger.trace("节点"+newTNode.key+"小于节点"+superior.key+",向左子树移动");
      if(superior.left == null){
        superior.left = newTNode;
        newTNode.superior = superior;
        logger.trace("节点"+newTNode.key+"临时插入"+superior.key+"的左子节点");
      }else{
        TreeNode<K,V> tempGrand = superior.superior;
        superior = insertTreeNode(superior.left,newTNode);
        superior.superior = tempGrand;
        if(tempGrand != null) {
          tempGrand.left = superior;
        }
        newTNode = superior.left;
      }

      left = true;
    }else{
      logger.trace("节点"+newTNode.key+"大于节点"+superior.key+",向右子树移动");
      if(superior.right == null){
        superior.right = newTNode;
        newTNode.superior = superior;
        logger.trace("节点"+newTNode.key+"临时插入"+superior.key+"的右子节点");
      }else{
        TreeNode<K,V> tempGrand = superior.superior;
        superior = insertTreeNode(superior.right,newTNode);
        superior.superior = tempGrand;
        if(tempGrand != null)
          tempGrand.right = superior;
        newTNode = superior.right;
      }

      left = false;
    }


    return balanceRBTree(superior, newTNode, left);
  }

4.红黑树平衡

黑红树的平衡实际上每次只对三层内的节点操作,如下图
1、
在这里插入图片描述
2、在这里插入图片描述
3、在这里插入图片描述
4、在这里插入图片描述

代码如下:

/**
   * 对三代节点做操作,保持红黑树平衡
   * 
   * 红黑树平衡操作规律:
   * 插入a节点
   * <p>1.若插入后父节点是红色,且有红色叔节点,则爷节点变红,父叔节点变黑,若爷节点是根节点,变黑</p>
   * <p>2.若操作后父节点是红色,且没有红色叔节点,且a和父节点同向,则父和爷节点左/右旋,互换颜色</p>
   * <p>3.若操作后父节点是红色,且没有红色叔节点,且a和父节点反向,则先a和父节点左/右旋,再父和爷节点左/右旋,互换颜色</p>
   * <p>其他直接插入</p>
   * 
   * @param superior
   * @param newTNode
   * @param left
   * @return
   */
  private TreeNode<K, V> balanceRBTree(TreeNode<K, V> superior, TreeNode<K, V> newTNode, boolean left) {

    TreeNode<K, V> grand,lUncle,rUncle,temp;
    grand = superior.superior;

    if(grand != null) {
      lUncle = superior.superior.left;
      rUncle = superior.superior.right;
      //若操作后父节点是红色
      if(superior.red && newTNode.red){
        //若有红叔节点
        if(lUncle != null && rUncle != null && lUncle.red && rUncle.red){
          blackDown(grand,lUncle,rUncle);
          logger.trace("节点"+rUncle.key+"和"+lUncle.key+"变黑,"+grand.key+"节点变红");
        }else {
          if (superior != (left ? superior.superior.left : superior.superior.right)) {//父节点是反向节点
            //向父节点的方向旋
            logger.trace("节点" + superior.key + (left ? "右旋" : "左旋"));
            superior = left ? rotateRight(superior, newTNode) : rotateLeft(superior, newTNode);
            //父爷节点再旋回来,互换颜色
            logger.trace("节点" + grand.key + (left ? "左旋" : "右旋"));
            temp = grand;//假设superior——>a地址内存,temp,grand——>b地址内存
            grand = left ? rotateLeft(grand, superior) : rotateRight(grand, superior);//grand——>a地址内存
            grand.red = false;
            temp.red = true;//temp依旧指向——>b地址内存
            logger.trace("节点" + temp.key + "变红,节点"+grand.key+"变黑");
          } else {//父子节点方向相同
            //父爷节点左旋,互换颜色
            logger.trace("节点" + grand.key + (left ? "右旋" : "左旋"));
            temp = grand;
            grand = left ? rotateRight(grand, superior) : rotateLeft(grand, superior);
            grand.red = false;
            temp.red = true;//temp依旧指向——>b地址内存
            logger.trace("节点" + temp.key + "变红,节点"+grand.key+"变黑");
          }
        }
      }
      return grand;
    }else{
      //根节点变黑
      superior.red = false;
      logger.trace("返回根节点(黑色):" + superior.key);
      return superior;
    }
  }

5.左旋、右旋和交换颜色

/**
   * 左旋,右子节点升为父节点,原父节点变成左子节点。原爷节点变为右节点的父节点
   * 若原右节点的左子节点变成原父节点的右子节点。
   * @param superior
   * @param right
   * @return
   */
  private TreeNode<K,V> rotateLeft(TreeNode<K,V> superior, TreeNode<K,V> right) {
    TreeNode<K,V> tempLeft = right.left;
    right.left=superior;
    right.superior = superior.superior;
    superior.right = tempLeft;
    superior.superior = right;
    if(tempLeft != null)
      tempLeft.superior = superior;
    return right;
  }

  /**
   * 右旋,参考左旋
   * @param superior
   * @param left
   * @return
   */
  private TreeNode<K,V> rotateRight(TreeNode<K,V> superior, TreeNode left) {
    TreeNode<K,V> tempRight = left.right;
    left.right=superior;
    left.superior = superior.superior;
    superior.left = tempRight;
    superior.superior = left;
    if(tempRight != null)
      tempRight.superior = superior;
    return left;
  }

  /**
   * 交换颜色
   * 
   * 父节点变红,子节点变黑
   * @param grand
   * @param lUncle
   * @param rUncle
   */
  private void blackDown(TreeNode<K,V> grand, TreeNode<K,V> lUncle, TreeNode<K,V> rUncle) {
    grand.red = true;
    lUncle.red = false;
    rUncle.red = false;
  }

8.测试验证

实际上我做了大量的单元测试,抽其中一段代码展示

  @Test
  void insertTreeNode_6() throws Exception {
  	//反射私有方法
	testInsertTreeNode = 
		MyHashMap.class.getDeclaredMethod("insertTreeNode", MyHashMap.TreeNode.class, MyHashMap.TreeNode.class);
    testInsertTreeNode.setAccessible(true);
    //创建节点
    node21 = myHashMap.new<String,String> Node<String,String>(null,"21","21",(int) testHash.invoke(myHashMap,"1"));
    node31 = myHashMap.new<String,String> Node<String,String>(null,"31","31",(int) testHash.invoke(myHashMap,"21"));
    node11 = myHashMap.new<String,String> Node<String,String>(null,"11","11",(int) testHash.invoke(myHashMap,"22"));
    node10 = myHashMap.new<String,String> Node<String,String>(null,"10","10",(int) testHash.invoke(myHashMap,"22"));
    node12 = myHashMap.new<String,String> Node<String,String>(null,"12","12",(int) testHash.invoke(myHashMap,"22"));
    node32 = myHashMap.new<String,String> Node<String,String>(null,"32","32",(int) testHash.invoke(myHashMap,"31"));
    node22 = myHashMap.new<String,String> Node<String,String>(null,"22","22",(int) testHash.invoke(myHashMap,"32"));
    node33 = myHashMap.new<String,String> Node<String,String>(null,"33","33",(int) testHash.invoke(myHashMap,"32"));
    node30 = myHashMap.new<String,String> Node<String,String>(null,"30","30",(int) testHash.invoke(myHashMap,"32"));

    t21 = myHashMap.new<String,String> TreeNode<String,String>(node21);
    t31 = myHashMap.new<String,String> TreeNode<String,String>(node31);
    t11 = myHashMap.new<String,String> TreeNode<String,String>(node11);
    t10 = myHashMap.new<String,String> TreeNode<String,String>(node10);
    t12 = myHashMap.new<String,String> TreeNode<String,String>(node12);
    t22 = myHashMap.new<String,String> TreeNode<String,String>(node22);
    t32 = myHashMap.new<String,String> TreeNode<String,String>(node32);
    t33 = myHashMap.new<String,String> TreeNode<String,String>(node33);
    t30 = myHashMap.new<String,String> TreeNode<String,String>(node30);
    //构造树
    /*
            黑11
           /  \
        黑10  红21
              /  \
           黑12	 黑30
                / \
            红22  红31

     */
    t11.left = t10;
    t11.right = t21;
    t11.red = false;

    t10.superior = t11;
    t10.red = false;

    t21.superior = t11;
    t21.red = true;
    t21.left=t12;
    t21.right=t30;

    t12.superior=t21;
    t12.red=false;

    t30.superior=t21;
    t30.left=t22;
    t30.right=t31;
    t30.red=false;

    t22.superior=t30;
    t22.red=true;

    t31.superior=t30;
    t31.red=true;
    /*插入32节点:
               黑11                       黑11                    黑21
               /  \                       /  \                 	/    \
        	黑10  红21                  黑10  红21            红11  	 红30
                  /  \      ————>         	 /  \    ————>  /  \     /	\
            	黑12 黑30                  黑12	黑30      黑10 黑12 黑22 黑31
    		         /  \                  	     / \                 	  \
    		       红22 红31                   红22 红31                   红32
                                                     \
                                                     红32
   */
    MyHashMap<String,String>.TreeNode<String,String> top  = (MyHashMap<String, String>.TreeNode<String, String>) testInsertTreeNode.invoke(myHashMap, t11, t32);

    assertAll("红黑树平衡测试:",
            ()->assertEquals(t21.key,top.key),
            ()->assertEquals(false,t21.red),
            ()->assertEquals(t11.key, top.left.key),
            ()->assertEquals(true,top.left.red),
            ()->assertEquals(t10.key, top.left.left.key),
            ()->assertEquals(false,top.left.left.red),
            ()->assertEquals(t12.key, top.left.right.key),
            ()->assertEquals(false,top.left.right.red),

            ()->assertEquals(t30.key, top.right.key),
            ()->assertEquals(true,top.right.red),
            ()->assertEquals(t22.key, top.right.left.key),
            ()->assertEquals(false,top.right.left.red),
            ()->assertEquals(t31.key, top.right.right.key),
            ()->assertEquals(false,top.right.right.red),
            ()->assertEquals(t32.key, top.right.right.right.key),
            ()->assertEquals(true ,top.right.right.right.red),

            ()->assertNull(top.left.left.left),
            ()->assertNull(top.left.left.right),
            ()->assertNull(top.left.right.left),
            ()->assertNull(top.left.right.right),
            ()->assertNull(top.right.left.left),
            ()->assertNull(top.right.left.right),
            ()->assertNull(top.right.right.left),
            ()->assertNull(top.right.right.right.left),
            ()->assertNull(top.right.right.right.right)
    );
  }

测试结果为绿色通过。


总结

文中为了方便理解和阅读,代码中大量使用了递归,递归的效率会比使用迭代差。实际上源码主要是用的迭代方式处理,而且还包含了删除等其他操作,相比较更为复杂。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用\[1\]和\[2\]提供了关于HashMap的实现原理的信息。HashMap在存储键值对时,会根据键的哈希值将其放入对应的桶。当发生哈希碰撞时,如果链表长度超过一定阈值,HashMap会将链表转换为,以提高查找效率。是一种自平衡的二叉搜索,它的节点包含指向父节点、左孩子、右孩子和前驱节点的指针,以及一个表示节点颜色的标志位。的插入和删除操作都能在O(log n)的时间复杂度内完成,因此可以提高HashMap的性能。\[1\]\[2\] 引用\[3\]提到HashMap的值存储在以键的哈希值为头的链表。这种数据结构在获取值时非常快速。因此,HashMap使用链表和的结合来实现高效的键值对存储和查找。\[3\] 综上所述,HashMap使用链表和的结构来存储键值对,以提高查找效率。当链表长度超过阈值时,链表会转换为。这种设计使得HashMap能够在O(1)的时间复杂度内进行键值对的存储和查找操作。 #### 引用[.reference_title] - *1* *3* [HashMap](https://blog.csdn.net/qq_40645822/article/details/91139215)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [HashMap详解](https://blog.csdn.net/X6954636/article/details/119705176)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值