STL 源码分析: RB_tree 红黑树(三) 插入和查找

不管什么样的插入,在插入新的节点后都考虑红黑树的四条约束是否被破坏,于是需要通过更改节点的颜色和子树的形状来使得红黑树的四条性质重新获得满足。

首先介绍两个旋转函数:左旋和右旋。
我们先看两个函数的原型:

inline void __rb_tree_rotate_left(__rb_tree_node_base* x, __rb_tree_node_base*& root);
inline void __rb_tree_rotate_right(__rb_tree_node_base* x, __rb_tree_node_base*& root);

两个函数都接受两个参数:x和root,其中x是待旋转的节点。root是当前红黑树的树根。

注意无论是左旋还是右旋,都不会修改节点的颜色和数据域,只修改节点的指针域。

那么给定一个x,它是咋左旋和右旋的呢?

左旋的意思是:使得当前节点成为某个相邻节点左孩子。既然要使得它成为个左孩子,说明当前它是某个节点右孩子。如下图所示:
在这里插入图片描述
给定一个节点,它的左旋方式就确定了。
例如,要对A左旋。那么A现在已经root的左孩子了,肯定不是A和root之间的旋转。那么还能成为那个相邻节点的左孩子呢?显然,A只可能成为D的左孩子了。
为啥A不能是I的左孩子呢? 因为A与I不相邻。
于是对A左旋的结果就是:
在这里插入图片描述

同理,右旋自然是把当前节点变成某个相邻节点的右孩子。例如对于上面的例子,如果对A进行右旋。
在这里插入图片描述
为啥A只能成为C的右孩子?而不能成为D子树里面的某个右孩子呢?这是因为A小于等于D的所有节点,因此A不可能在D子树成为某个右节点。它只能去C子树那里兴风作浪!

左旋

inline void 
__rb_tree_rotate_left(__rb_tree_node_base* x, __rb_tree_node_base*& root)
{
  __rb_tree_node_base* y = x->right;
  x->right = y->left;
  if (y->left !=0)
    y->left->parent = x;
  y->parent = x->parent;

  if (x == root)
    root = y;
  else if (x == x->parent->left)
    x->parent->left = y;
  else
    x->parent->right = y;
  y->left = x;
  x->parent = y;
}

右旋

inline void 
__rb_tree_rotate_right(__rb_tree_node_base* x, __rb_tree_node_base*& root)
{
  __rb_tree_node_base* y = x->left;
  x->left = y->right;
  if (y->right != 0)
    y->right->parent = x;
  y->parent = x->parent;

  if (x == root)
    root = y;
  else if (x == x->parent->right)
    x->parent->right = y;
  else
    x->parent->left = y;
  y->right = x;
  x->parent = y;
}

插入

rb_tree里面有两类插入函数,

  pair<iterator,bool> insert_unique(const value_type& x);
  iterator insert_equal(const value_type& x);

一个是insert_unique(),另外一个是insert_equal();insert_unique就是整颗树里面,插入的key是独一无二的;而insert_equal()表示可以同时存在多个具有相同的key的节点。

insert_unique(const Value & v)

先看,insert_unique函数,该函数把给定的value插入到一棵树里面去。

template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
pair<typename rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::iterator, bool>
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::insert_unique(const Value& v)
{
  link_type y = header;
  link_type x = root();
  bool comp = true;
  while (x != 0) {
    y = x;
    comp = key_compare(KeyOfValue()(v), key(x));
    x = comp ? left(x) : right(x);
  }
  iterator j = iterator(y);   
  if (comp)
    if (j == begin())     
      return pair<iterator,bool>(__insert(x, y, v), true);
    else
      --j;
  if (key_compare(key(j.node), KeyOfValue()(v)))
    return pair<iterator,bool>(__insert(x, y, v), true);
  return pair<iterator,bool>(j, false);
}

首先,执行

 while (x != 0) {
    y = x;
    comp = key_compare(KeyOfValue()(v), key(x));
    x = comp ? left(x) : right(x);
  }

这段代码的意思是,根据value的值提取出它对应的key,然后根据key定位到待插入节点的位置。
while结束是,x为NULL也就是待插入点,而y为插入点的父节点。

接下来初始化一个迭代器j.

  iterator j = iterator(y);   
  if (comp)
    if (j == begin())     
      return pair<iterator,bool>(__insert(x, y, v), true);
    else
      --j;

如果发现comp为true,这意味着x应该插入到y的左孩子,否则应该插入到右孩子。

如果comp为true,而且刚好j又是整个树的最小值,这说明当前要插入的值比整个树的最小值还要小。于是就直接调用__insert(x, y, v) 函数进行插入。整个辅助函数,我们后面深入。

如果comp为true,但是y不是指向最小值。那么就让 j j 自减一下,此时 j j 指向的元素,此时有关系: j . v a l u e < y . v a l u e j.value < y.value 而且一定会有 v > = j . v a l u e v>=j.value 。如果 v < j . v a l u e v<j.value ,那么在执行while循环的时候就应该找到是 j j 而不是 y y .
接下来:

  if (key_compare(key(j.node), KeyOfValue()(v)))
    return pair<iterator,bool>(__insert(x, y, v), true);

比较 j . v a l u e j.value v v 之间那个大。
如果条件为true,说明此时 j . v a l u e < v j.value < v ,OK,说明v是一个全新的元素。执行正常的插入辅助程序。
如果条件为false,意味着 j . v a l u e > = v j.value>=v ,又因为 v > = j . v a l u e v>=j.value ,联立这两个条件,必然有 j . v a l u e = = v j.value==v ,说明这个v就是一个重复的元素,于是直接执行

return pair<iterator,bool>(j, false);

表示这是个重复的元素,没有执行插入操作。这里实在是太巧妙啦!!!!!
如何在数学上证明 x = y x=y ?只需分别证明: x > = y x>=y x < = y x<=y 就可以了!

insert_equal( const Value &v)

可以插入重复的元素

template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
typename rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::iterator
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::insert_equal(const Value& v)
{
  link_type y = header;
  link_type x = root();
  while (x != 0) {
    y = x;
    x = key_compare(KeyOfValue()(v), key(x)) ? left(x) : right(x);
  }
  return __insert(x, y, v);
}

这个就很简单粗暴啦,直接寻找应该插入的位置。不管到底有没有重复。

__insert(base_ptr x,base_ptr y,const Value & v)

这是插入函数的幕后黑手。
函数接收三个参数, x x 是插入位置, y y 是插入点的父节点,v是新插入节点的值。

template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
typename rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::iterator
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::
__insert(base_ptr x_, base_ptr y_, const Value& v) {
  link_type x = (link_type) x_;
  link_type y = (link_type) y_;
  link_type z;

  if (y == header || x != 0 || key_compare(KeyOfValue()(v), key(y))) {
    z = create_node(v);
    left(y) = z;                // also makes leftmost() = z when y == header
    if (y == header) {
      root() = z;
      rightmost() = z;
    }
    else if (y == leftmost())
      leftmost() = z;           // maintain leftmost() pointing to min node
  }
  else {
    z = create_node(v);
    right(y) = z;
    if (y == rightmost())
      rightmost() = z;          // maintain rightmost() pointing to max node
  }
  parent(z) = y;
  left(z) = 0;
  right(z) = 0;
  __rb_tree_rebalance(z, header->parent);
  ++node_count;
  return iterator(z);
}

首先,判断当前红黑树是否为空,以及v是否应该插入左孩子。至于 x 0 x \neq 0 ,因为insert_equal和insert_unique都有个while循环,而while循环的终止条件就是 x = 0 x=0 ,因此在__insert函数中传入的 x x 永远都为NULL.

  if (y == header || x != 0 || key_compare(KeyOfValue()(v), key(y))) {
    z = create_node(v);
    left(y) = z;                // also makes leftmost() = z when y == header
    if (y == header) {
      root() = z;
      rightmost() = z;
    }
    else if (y == leftmost())
      leftmost() = z;           // maintain leftmost() pointing to min node
  }

如果满足这几个情况的其中给一个。
则,先创建新的节点z,并让z成为y的左孩子。
如果此时树刚好又是空的,那么就让z成为根节点,同时让header->right指向这个新节点。
如果树不为空,那么就看看y是不是当前的最小值,如果是说明待插入元素是个更小的点,那么就更新leftmost指针。

如果条件都不满足,说明插入点应该插到y的右边。于是让 z z 成为y的右孩子,同时更新rightmost

最后,再让y成为z的父亲,把z的左右孩子置为NULL.
再调用,__rb_tree_rebalance(z, header->parent);对红黑树进行平衡。
最终再让节点计数器增大。

__rb_tree_rebalance(__rb_tree_node_base *x,__rb_tree_node_base*& root)

树平衡函数,主要是在插入新的红色节点 x x 后,对以root为根的树的颜色和形状做调整,使得树依旧满足红黑树的4个约束。

inline void 
__rb_tree_rebalance(__rb_tree_node_base* x, __rb_tree_node_base*& root)
{
  x->color = __rb_tree_red;
  while (x != root && x->parent->color == __rb_tree_red) {
    if (x->parent == x->parent->parent->left) {
      __rb_tree_node_base* y = x->parent->parent->right;
      if (y && y->color == __rb_tree_red) {
        x->parent->color = __rb_tree_black;
        y->color = __rb_tree_black;
        x->parent->parent->color = __rb_tree_red;
        x = x->parent->parent;
      }
      else {
        if (x == x->parent->right) {
          x = x->parent;
          __rb_tree_rotate_left(x, root);
        }
        x->parent->color = __rb_tree_black;
        x->parent->parent->color = __rb_tree_red;
        __rb_tree_rotate_right(x->parent->parent, root);
      }
    }
    else {
      __rb_tree_node_base* y = x->parent->parent->left;
      if (y && y->color == __rb_tree_red) {
        x->parent->color = __rb_tree_black;
        y->color = __rb_tree_black;
        x->parent->parent->color = __rb_tree_red;
        x = x->parent->parent;
      }
      else {
        if (x == x->parent->left) {
          x = x->parent;
          __rb_tree_rotate_right(x, root);
        }
        x->parent->color = __rb_tree_black;
        x->parent->parent->color = __rb_tree_red;
        __rb_tree_rotate_left(x->parent->parent, root);
      }
    }
  }
  root->color = __rb_tree_black;
}

第一句,首先把新插入的节点 x x 染成红色。

x->color = __rb_tree_red;

为啥得把新节点染成红色呢? 这是为了使得红黑树的第4条约束:对于红黑树的任一节点,从该节点出发到达叶子的NULL指针的所有简单路径上的黑色节点个数是相等的。

在插入 x x 前,因为目标本来就是颗红黑树,因此那四个约束全部满足,自然而且第四条对路径上黑点的约束也满足。如果插入的 x x 是个黑色的,那么绝对会破坏第四条约束。这是不好的。

第二句开始就是个while循环。
当把新插入的节点无脑涂成红色的时候,第四条约束可以很方便的满足,但是却有可能出现新插入的节点和它的父节点同时为红的情况。这个是要就要做出调整了。

接下来就是分情况讨论:
第一种最好的情况,新插入的节点的父亲是个黑色的。
在这里插入图片描述
假设H节点是新插入的节点,把它染成红色是没有问题的。于是while可以直接不执行,得到:
在这里插入图片描述

假设H要插入到I的左边,而且它的父亲是个红色的,此时会出现两个连续的红色节点,需要调整。
在这里插入图片描述
此时,H的父亲I是D节点的左孩子。
对应到代码里面,于是y指向J节点。
而J的颜色又是红色的,于是把D的黑色往下沉,并把x指向D节点。得到:
在这里插入图片描述
注意,以x为根的子树都是向下满足条件的,而且以x为根的子树的黑高与原来子树的黑高相等。
都是x可能向上发生冲突,就如这里的,D变成红色以后又与A一起出现了两个相邻的红色节点。
在下一轮while循环中将会处理这个D的冲突。
注意到当D变成红色后,跟A的红色冲突,而A的父亲是黑色的,A的兄弟B是红色的,于是一样的套路,把root的黑色下沉。
在这里插入图片描述
此时x就是root了,不满足while循环条件,退出循环。
最后,再把root染成黑色。

在这里插入图片描述
于是这就完成了对 H的插入。

假设在下图中插入Q
在这里插入图片描述
把Q插入后,与G的红色形成了冲突了,调整为下图:
注意到此时,C的右孩子为空。
于是,首先对G做一次左旋:
在这里插入图片描述
接着,调整Q和C的颜色
在这里插入图片描述
再做一次右旋:
在这里插入图片描述
再下一轮,while循环的时候G的父亲就不是红色了,于是直接退出循环。
在插入个P.
在这里插入图片描述
G,P都是红色,冲突,解决冲突:
在这里插入图片描述
再插入个R.
在这里插入图片描述
R,P都是红色,冲突了。

发布了307 篇原创文章 · 获赞 268 · 访问量 56万+

猜你喜欢

转载自blog.csdn.net/jmh1996/article/details/103466017