源码解析系列:HashMap(4) -红黑树的删除方面的操作


1. 前言

HashMap(4)中介绍了HashMap中对于红黑树的一些操作,主要是remove操作,同样在这一篇中对于源码的红黑树操作不会详细介绍,在最后一篇的C语言实现HashMap中会解释红黑树的插入删除的原理这些,这篇是对这些方法的一个大概了解
第一篇文章:源码解析系列:HashMap(1)
第二篇文章:源码解析系列:HashMap(2)
第三篇文章:源码解析系列:HashMap(3)
第四篇文章:源码解析系列:HashMap(4)
第五篇文章:源码解析系列:HashMap(5)



2. 方法解析

1. removeTreeNode

1、官方

 final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
                                 boolean movable) {
      //n:数组长度
      int n;
      //table数组没有东西直接返回
      if (tab == null || (n = tab.length) == 0)
          return;
      //下标位置
      int index = (n - 1) & hash;
      //第一个结点
      TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;
      //succ :当前结点的下一个结点	pred :当前结点的前一个结点
      TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;
      //前一个结点为NULL
      if (pred == null)
      	  //证明是第一个结点
          tab[index] = first = succ;
      else
          //否则就是一个链表了
          pred.next = succ;
      if (succ != null)
      	  //同样比如a-c-b,要删除c
      	  //就把b的前一个设置为a,a的下一个设置为b
          succ.prev = pred;
      //如果第一个是空,直接返回,证明没有数据
      if (first == null)
          return;
      //重新找根结点,root指向第一个结点,有可能此时的第一个链表结点不是根
      if (root.parent != null)
          root = root.root();
      //下面几种情况表示节点数小于=6
      //1. root是空
      //2. root的右节点为空
      //			 B
      //		   / 
      //		  R	
      //3. root的左节点为空,那么和上面一样了
      //4. root的左孩子的左孩子为空
      //			那么下面这种情况为什么不可以存在呢?没想明白这点
      //			B
      //		  /  \ 
      //		B	  R
      //	  	  	/	\
      //			B	 B
      //				 / \
      //				R	R
      //猜测可能是在删除到这里的时候对下面三行的树进行调整时,把第根结点的R换成了B
      if (root == null
          || (movable
              && (root.right == null
                  || (rl = root.left) == null
                  || rl.left == null))) {
          tab[index] = first.untreeify(map);  // too small
          return;
      }
      //下面开始删除结点,下面有时间再填坑把,这里我给出我写的c语言实现的删除
      TreeNode<K,V> p = this, pl = left, pr = right, replacement;
      if (pl != null && pr != null) {
          TreeNode<K,V> s = pr, sl;
          while ((sl = s.left) != null) // find successor
              s = sl;
          boolean c = s.red; s.red = p.red; p.red = c; // swap colors
          TreeNode<K,V> sr = s.right;
          TreeNode<K,V> pp = p.parent;
          if (s == pr) { // p was s's direct parent
              p.parent = s;
              s.right = p;
          }
          else {
              TreeNode<K,V> sp = s.parent;
              if ((p.parent = sp) != null) {
                  if (s == sp.left)
                      sp.left = p;
                  else
                      sp.right = p;
              }
              if ((s.right = pr) != null)
                  pr.parent = s;
          }
          p.left = null;
          if ((p.right = sr) != null)
              sr.parent = p;
          if ((s.left = pl) != null)
              pl.parent = s;
          if ((s.parent = pp) == null)
              root = s;
          else if (p == pp.left)
              pp.left = s;
          else
              pp.right = s;
          if (sr != null)
              replacement = sr;
          else
              replacement = p;
      }
      else if (pl != null)
          replacement = pl;
      else if (pr != null)
          replacement = pr;
      else
          replacement = p;
      if (replacement != p) {
          TreeNode<K,V> pp = replacement.parent = p.parent;
          if (pp == null)
              root = replacement;
          else if (p == pp.left)
              pp.left = replacement;
          else
              pp.right = replacement;
          p.left = p.right = p.parent = null;
      }

      TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);

      if (replacement == p) {  // detach
          TreeNode<K,V> pp = p.parent;
          p.parent = null;
          if (pp != null) {
              if (p == pp.left)
                  pp.left = null;
              else if (p == pp.right)
                  pp.right = null;
          }
      }
      if (movable)
          moveRootToFront(tab, r);
  }

2、自己写的一个删除方法:删除和删除调整(c语言)

当时在做课设的时候官方的看不太懂,所以就自己去找了别人的代码和教程来看结合来写了一个。源码是把维护双链表和删除结点放在一起,我这里时分成三部分:维护双向链表-删除结点,并用后继节点进行替换-删除修复红黑树。完整的代码在后面的c语言实现也会给出。


1、维护双链表

//删除叶子结点
void deleteNode(hashmap& hashmap, int hash, Key key, Value value, hashNode& root, hashNode node) {
    if (hashmap == NULL || hashmap->table == NULL || hashmap->length == 0) {
        return;
    }
    //获取下标
    int index = (hashmap->length - 1) & hash;
    hashNode table = hashmap->table;
    //首个节点
    hashNode first = table[index].next;
    hashNode realRoot = first;
    //succ-调用这个方法的节点(待删除节点)的后驱节点
    hashNode succ = node->next;
    //prev - 调用这个方法的节点(待删除节点)的前驱节点
    hashNode pred = node->prev;
    /**
    * 维护双向链表(map在红黑树数据存储的过程中,除了维护红黑树之外还对双向链表进行了维护)
    * 从链表中将该节点删除
    * 如果前驱节点为空,说明删除节点是头节点,删除之后,头节点直接指向了删除节点的后继节点
    */
    if (pred->data.value == UN_EXIT) {
        table[index].next = first = succ;
    }
    else {
        //删除的不是头节点
        pred->next = succ;
    }
    if (succ != NULL) {
        succ->prev = pred;
    }
    // 如果头节点(即根节点)为空,说明该节点删除后,红黑树为空,直接返回
    if (first == NULL) {
        return;
    }
    // 如果父节点不为空,说明删除后,调用root方法重新获取当前树的根节点
    if (first->parent != NULL) {
        realRoot = getRoot(first);
    }
    /**
    * 当以下三个条件任一满足时,当满足红黑树条件时,说明该位置元素的长度少于6(UNTREEIFY_THRESHOLD),需要对该位置元素链表化
    * 1、root == NULL:根节点为空,树节点数量为0
    * 2、root.rchild == NULL:右孩子为空,树节点数量最多为2
    * 3、(rl = root.left) == null || rl.left == null):
    *      (rl = root.left) == null:左孩子为空,树节点数最多为2
    *      rl.left == null:左孩子的左孩子为NULL,树节点数最多为6
    */
    if (hashmap->size <= UNTREEIFY_THRESHOLD) {
        table[index].next = untreeify(hashmap, first);
        hashmap->table = table;
        return;
    }
    hashNode var = package(key, value);
    //否则还是树,调用树的删除
    deleteNodeReal(var, root, hashmap);
    //重新赋值table
    hashmap->table = table;
    //重新赋值根节点
    if (root->parent != NULL) {
        root = realRoot;
    }
}

2、删除树结点,并且使用后继结点替换

//真正的删除
void deleteNodeReal(hashNode node, hashNode& root, hashmap& hashMap) {
    hashNode x = NULL;  // x 指向 y 的唯一子节点或指向 NULL
    hashNode z = searchNode(node->data.key, root);
    hashNode parent = NULL;
    hashNode y = z; //y 为从树中删除的节点或者移至树内的节点
    int y_color = -1;   //保存y的颜色
    if (z == NULL) {
        //没找到
        return;
    }
    //y的颜色
    y_color = y->isRed;
    if (z->lchild == NULL) {
        //情况1:被删除的z左节点是空,右孩子无限制,使用右孩子替换
        x = z->rchild;
        //使用z的右孩子替换
        transplate(z, z->rchild, root);
        parent = z->parent;
    }
    else if (z->rchild == NULL) {
        //情况2:右孩子为空,使用左孩子替换
        x = z->lchild;
        //使用z的右孩子替换
        transplate(z, z->lchild , root);
        parent = z->parent;
    }
    else {
        //z有两个结点
        /*
        ①、找到结点D的后继结点S

        ②、将结点S的key值赋给结点D;

        ③、再将结点S从树中删除,并用结点S的右孩子替代结点S的位置;[注:从前面的描述可以看出,其实被删的是结点D的后继结点S]

        ④、如果被删结点S为红色,则红黑树性质未被破坏,因此不需做其他调整;

        ⑤、如果被删结点S为黑色,则需进一步做调整处理。
        */

        //y是找到的后继结点,y的左孩子一定是NULL
        y = searchMin(z->rchild);
        //记录被删除的颜色
        y_color = y->isRed;
        x = y->rchild;
        if (y->parent == z) {
            //z的后继节点是y,证明y是z的右孩子结点,并且y没有左孩子结点
            if (x == NULL) {
                y->parent->rchild = NULL;
            }
            else {
                x->parent = y;
            }
            //y取代z
            transplate(z, y, root);
            y->lchild = z->lchild;
            y->lchild->parent = y;
            y->isRed = z->isRed;
            //这里的parent是因为直接取到z的孩子结点
            parent = y;
        }
        else {
            //y取代z
            hashNode yRchild = y->rchild;
            parent = y->parent;
            //如果右结点为null,那么直接赋值
            if (yRchild == NULL) {
                y->parent->lchild = NULL;
            }
            else {
                //否则就替换2次
                transplate(y, yRchild, root);
            }
            transplate(z, y, root);
            //y结点要替换到z结点的位置,赋值右子树
            y->lchild = z->lchild;
            y->lchild->parent = y;
            y->isRed = z->isRed;
            y->rchild = z->rchild;
            y->rchild->parent = y;
        }
    }
    //判断如果删除的结点是黑结点,进行修复
    //对x进行修复,因为x是填充到y位置的
    if (y_color == BLACK) {
        deleteFixUp(x, parent, root);
    }
    //删除之后要调整头部
    moveRootToFront(hashMap->table, root, hashMap);
    //释放删除的结点z
    free(z);
    z = NULL;
    return;
}

3、删除修复

//删除修复
//如果删除的结点时红色结点,不用调整平衡,如果删除的结点时黑节点,就要调整
/*
        前提1:参照结点N为父结点P的左孩子
            情况1:参照结点N的兄弟B是红色的
            处理过程:
                ①、将父结点P的颜色改为红色,兄弟结点的颜色改为黑色;
                ②、以父结点P为支点进行左旋处理;
                ③、情况1转变为情况2或3、4,后续需要依次判断处理。

            情况2:参照结点N的兄弟B是黑色的,且B的两个孩子都是黑色的
            处理过程:
                ①、如果parent此时为红,则把brother的黑色转移到parent上
                ②、如果此时parent为黑,即此时全黑了,则把brother涂红,导致brother分支少一个黑,使整个分支都少了一个黑,需要对parent又进行一轮调整

            情况3:参照结点N的兄弟B是黑色的,且B的左孩子是红色的,右孩子是黑色的
            处理过程:
                ①、将兄弟结点的左孩子调整为父颜色,父颜色调整为黑色
                ②、以兄弟结点开始右旋
                ③、以父节点左旋

            情况4:参照结点N的兄弟B是黑色的,且B的左孩子是黑色的,右孩子是红色的
            处理过程:
                ①、将父结点P的颜色拷贝给兄弟结点B,再将父结点P和兄弟结点的右孩子BR的颜色改为黑色;
                ②、以父结点P为支点,进行左旋处理;
                ③、将该结点改为树的根结点,也意味着调整结束。


         前提2:参照结点N为父结点P的右孩子
            情况5:参照结点N的兄弟B是红色的
            处理过程:
                ①、将父结点P的颜色改为红色,兄弟结点的颜色改为黑色;
                ②、以父结点P为支点进行右旋处理;
                ③、情况5转变为情况6或7、8,后续需要依次判断处理。   

            情况6:参照结点N的兄弟B是黑色的,且B的两个孩子都是黑色的
            处理过程:
                ①、如果parent此时为红,则把brother的黑色转移到parent上
                ②、如果此时parent为黑,即此时全黑了,则把brother涂红,导致brother分支少一个黑,使整个分支都少了一个黑,需要对parent又进行一轮调整

            情况7:参照结点N的兄弟B是黑色的,且B的右孩子是红色的,左孩子是黑色的
            处理过程:
                ①、将兄弟结点的右孩子调整为父颜色,父颜色调整为黑色
                ②、以兄弟结点开始左旋
                ③、以父节点右旋

            情况8:参照结点N的兄弟B是黑色的,且B的右孩子是黑色的,左孩子是红色的
            处理过程:
                ①、将父结点P的颜色拷贝给兄弟结点B,再将父结点P和兄弟结点的右孩子BR的颜色改为黑色;
                ②、以父结点P为支点,进行右旋处理;
                ③、将该结点改为树的根结点,也意味着调整结束。
*/
void deleteFixUp(hashNode x, hashNode parent, hashNode& root) {
    hashNode brother = NULL;
    //如果要修复的树是根,由于没有兄弟姐弟啊,那么把x的颜色改成黑色就好了
    while ((x == NULL || x->isRed == BLACK) && x != root) {
        // 前提1:参照结点x为父结点P的左孩子
        hashNode p = parent;
        if (x == p->lchild) {
            //兄弟是右结点
            brother = p->rchild;
            //情况1:参照结点N的兄弟B是红色的
            /*
                ①、将父结点P的颜色改为红色,兄弟结点的颜色改为黑色;
                ②、以父结点P为支点进行左旋处理;
                ③、情况1转变为情况2或3、4,后续需要依次判断处理。
            */
            if (brother != NULL && brother->isRed == RED) {
                brother->isRed = BLACK;
                p->isRed = RED;
                //以父节点左旋
                leftRotate(root, p);
                //完成之后兄弟要重新更新进行下一步操作
                brother = p->rchild;
            }
            // 情况2:参照结点N的兄弟B是黑色的,且B的两个孩子都是黑色的
            /*
                ①、将兄弟结点B的颜色改为红色
                ②、情况2处理完成后,不必再进行情况3、4的判断,但需重新循环判断前提1、2。
            */
            if (brother != NULL && brother->isRed == BLACK && (brother->lchild == NULL || isBlack(brother->lchild)) && (brother->rchild == NULL || isBlack(brother->rchild))) {
                brother->isRed = RED;
                // 如果parent此时为红,则把brother的黑色转移到parent上
                if (p->isRed = RED) {
                    p->isRed = BLACK;
                    brother->isRed = RED;
                    break;
                }
                else {
                    // 如果此时parent为黑,即此时全黑了,则把brother涂红,导致brother分支少一个黑,使整个分支都少了一个黑,需要对parent又进行一轮调整
                    brother->isRed = RED;
                    x = parent;
                    parent = x->parent;
                }

                
            }
            else {
                // 情况3:参照结点N的兄弟B是黑色的,且B的左孩子是红色的
            /*
                ①、将兄弟结点的左孩子调整为父颜色,父颜色调整为黑色
                ②、以兄弟结点开始右旋
                ③、以父节点左旋
            */
                if (brother != NULL && brother->isRed == BLACK && RedOrNot(brother->lchild)) {
                    brother->lchild->isRed = p->isRed;
                    p->isRed = BLACK;
                    rightRotate(root, brother);
                    leftRotate(root, parent);
                }
                //情况4:参照结点N的兄弟B是黑色的,且B的左孩子是黑色的,右孩子是红色的
                /*
                    ①、将父结点P的颜色拷贝给兄弟结点B,再将父结点P和兄弟结点的右孩子BR的颜色改为黑色;
                    ②、以父结点P为支点,进行左旋处理;
                    ③、将该结点改为树的根结点,也意味着调整结束。

                */
                else if (brother != NULL && brother->isRed == BLACK && RedOrNot(brother->rchild)) {
                    brother->isRed = p->isRed;
                    p->isRed = BLACK;
                    brother->rchild->isRed = BLACK;
                    leftRotate(root, p);
                }
                break;
            }
        

        }

        // 前提2:参照结点x为父结点P的左孩子
        else {
            //兄弟是左结点
            brother = p->lchild;
            //情况5:参照结点N的兄弟B是红色的
            /*
                ①、将父结点P的颜色改为红色,兄弟结点的颜色改为黑色;
                ②、以父结点P为支点进行右旋处理;
                ③、情况5转变为情况6或7、8,后续需要依次判断处理。  
            */
            if (brother->isRed == RED) {
                p->isRed = RED;
                brother->isRed = BLACK;
                rightRotate(root, p);
                brother = p ->lchild;
            }

            //情况6:参照结点N的兄弟B是黑色的,且B的两个孩子都是黑色的
            /*
                ①、将兄弟结点B的颜色改为红色;
                ②、情况6处理完成后,不必再进行情况7、8的判断,但需要重新循环判断前提1、2。
            */
            if ((brother->lchild == NULL || brother->lchild->isRed == BLACK)
                && (brother->rchild == NULL || brother->rchild->isRed == BLACK)) {
                // 如果parent此时为红,则把brother的黑色转移到parent上
                if (p->isRed == RED) {
                    p->isRed = BLACK;
                    brother->isRed = RED;
                    break;
                }
                // 如果此时parent为黑,即此时全黑了,则把brother涂红,导致brother分支少一个黑,使整个分支都少了一个黑,需要对parent又进行一轮调整
                else {
                    brother->isRed = RED;
                    x = parent;
                    parent = x->parent;
                }
            }
            else {
                //左右孩子必有一个是红色
                    // 情况7:参照结点N的兄弟B是黑色的,且B的右孩子是红色的
                    /*
                        ①、将兄弟结点的右孩子调整为父颜色,父颜色调整为黑色
                        ②、以兄弟结点开始左旋
                        ③、以父节点右旋
                    */
                if (brother->isRed == BLACK && brother->rchild != NULL && brother->rchild->isRed == RED) {
                    brother->rchild->isRed = p->isRed;
                    p->isRed = BLACK;
                    leftRotate(root, brother);
                    rightRotate(root, p);

                }

                   
             //左右孩子必有一个是红色
                    // 情况8:参照结点N的兄弟B是黑色的,且B的左孩子是红色的
                    /*
                     ①、将父结点P的颜色拷贝给兄弟结点B,再将父结点P和兄弟结点的右孩子BR的颜色改为黑色;
                    ②、以父结点P为支点,进行右旋处理;
                    ③、将该结点改为树的根结点,也意味着调整结束。
                    */
                else if (brother != NULL && brother->isRed == BLACK && brother->lchild != NULL && RedOrNot(brother->lchild)) {
                    brother->isRed = p->isRed;
                    p->isRed = BLACK;
                    brother->lchild->isRed = BLACK;
                    //以父节点进行旋转
                    rightRotate(root, p);
                }
            }

        }
    }
    //修改x颜色为黑色
    if (x != NULL) {
        x->isRed = BLACK;
    }

    return;
}

2. untreeif

去树化,把红黑树去树化变成一个单向链表,当不高于去树化阈值6的时候就调用该方法。单链表的形成是根据原来红黑树中双链表来形成的。

 final Node<K,V> untreeify(HashMap<K,V> map) {
 			//两个赋值结点hd,tl
            Node<K,V> hd = null, tl = null;
            //q是当前结点
            for (Node<K,V> q = this; q != null; q = q.next) {
            	//使用普通的链表结点代替树结点,此时已经把双向链表和树的一些结点全部清为NULL
            	//比如prev,pred,lchild,rchild
                Node<K,V> p = map.replacementNode(q, null);
                //如果t1 == null,第一次进入
                if (tl == null)
                	//p作为头节点
                    hd = p;
                else
                	//next是单链表也有的,不会清除掉
                    tl.next = p;
                tl = p;
            }
            //hd是单链表的头结点
            return hd;
        }

3. split

分散树的结点,在作为红黑树进行数组扩容的时候,调用这个方法把原来树的结点分散到指定的下标上面。

 final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
 	  //b是当前结点
      TreeNode<K,V> b = this;
      //低位头结点尾节点,高位头结点和尾结点
      TreeNode<K,V> loHead = null, loTail = null;
      TreeNode<K,V> hiHead = null, hiTail = null;
      //低位结点个数和高位结点个数
      int lc = 0, hc = 0;
      //下面就是遍历双向链表了,其实下面也没涉及到红黑树的破坏,只是说把双向两表的结点分开,具体怎么分可以看第一篇HashMa,里面有详细介绍
      //e是当前结点
      for (TreeNode<K,V> e = b, next; e != null; e = next) {
      	  //next是e的下一个
          next = (TreeNode<K,V>)e.next;
          //e的下一个设置尾null
          e.next = null;
          //判断是高位还是低位
          if ((e.hash & bit) == 0) {
          	  //设置低位链表
              if ((e.prev = loTail) == null)
              	  //e的前一个设置尾null,这里也是第一次访问才可以进来设置头,loTail一开始肯定是null
                  loHead = e;
              else
              	  //设置链表尾
                  loTail.next = e;
              loTail = e;
              //低位链表数目+1
              ++lc;
          }
          else {
          	  //高位链表的设置,和低位一样,就不多说了
              if ((e.prev = hiTail) == null)
                  hiHead = e;
              else
                  hiTail.next = e;
              hiTail = e;
              ++hc;
          }
      }
	  //如果低位不为空
      if (loHead != null) {
      	  //判断如果低位的结点个数小于=6个,就去树化成为单链表
      	  //注意此时的低位链表和高位链表还保留红黑树的特征,因为是从树退化下来的
          if (lc <= UNTREEIFY_THRESHOLD)
              tab[index] = loHead.untreeify(map);
          else {
          	  //否则就设置是否要树化
              tab[index] = loHead;
              //高位头不为null,证明此时已经不是一棵完整的树了,要重新树化
              if (hiHead != null) // (else is already treeified)
                  loHead.treeify(tab);
          }
      }
      //如果高位不为空
      if (hiHead != null) {
      	  //如果数量 <= 6
          if (hc <= UNTREEIFY_THRESHOLD)
          	  //去树化
              tab[index + bit] = hiHead.untreeify(map);
          else {
          	  //否则设置为tab[index + bit],bit是旧数组容量
              tab[index + bit] = hiHead;
              //低位链表不为空,证明此时已经不是一棵完整的树了,要重新树化
              if (loHead != null)
                  hiHead.treeify(tab);
          }
      }
  }





如有错误,欢迎指出

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值