STL源码剖析(十二)关联式容器之树、红黑树

关联式容器
根据数据在容器中的排列,容器分为序列式容器和关联式容器。
标准的STL关联式容器分为set(集合)和map(映射表)两大类,以及衍生体multiset、multimap等。
这些容器的底层机制均以RB-tree(红黑树)完成。
RB-tree也是一个独立容器,但不开放在外界使用。

关联式容器,每个元素都有一个键值key和一个实值value,当元素插入到关联式容器中,容器内部结构便依照键值大小以某种特定规则将这个元素置于适当位置。
关联式容器没有头尾,只有最大元素和最小元素,所以不会有push_back()、push_front()、pop_back()、pop_front()、begin()、end()这样的操作。
关联式容器内部结构是一个平衡二叉树,以获得良好的搜寻效率。


在计算机科学里,树是一种十分基础的数据结构,几乎所有操作系统都将文件存放在树状结构里;几乎所有的编绎器都需要实现一个表达式树;文件压缩所用的哈夫曼算法需要用到树状结构;数据库所使用的B-tree则是一种相当复杂的树状结构。

二叉搜索树
编绎器表达式树和哈夫曼编码树都是二叉树。
二叉搜索树可提供对数时间的元素插入和访问。
二叉搜索树的节点放置规则是:任何节点的键值一定大于其左子树中的每一个节点的键值,并小于其右子树中的每一个节点的键值;因此,根节点一直往左走,直至无左路可走,即得最小元素;根节点一直往右走,直至无右路可走,即得最大元素。
要在二叉搜索树中插入元素时,可从根节点开始,遇键值较大者就向左,遇键值较小者就向右,一直到尾端,即为插入点。
移除元素时,欲删除旧节点A,如果A只有一个子节点,直接将A的子节点连至A的父节点,并将A删除;如果A有两个子节点,以右子树内的最小节点取代A。

平衡二叉搜索树
平衡的大致意义是没有任何一个节点过深,平衡二叉搜索树比一般二叉搜索树复杂,插入节点和删除节点时间也比较长,但是可以避免高度不平衡情况,元素的访问时间平均而言也比较少。

AVL tree
AVL tree平衡条件的建立是为了确保整棵树的深度为O(logN),要求任何节点的左右子树高度相差最多1。
当插入节点后平衡状态被破坏,可采用单旋转或双旋转调整解决。
在这里插入图片描述
在这里插入图片描述
单旋转
在这里插入图片描述
双旋转
在这里插入图片描述
RB-tree(红黑树)
RB-tree作为关联式容器底部机制使用,虽然平衡条件不同于AVL-tree,但同样运用了单旋转和双旋转修正操作。
RB-tree不仅是一个二叉搜索树,且必须满足以下规则:
1,每个节点不是红色就是黑色
2,根节点为黑色
3,如果节点为红,其子节点必须为黑
4,任一节点至NULL(树尾端)的任何路径,所含黑节点数必须相同。
规则4可推测新增节点必须为红,规则3推测新增节点父节点必须为黑,当新增节点根据二叉搜索树的规则到达插入点,却未能符合上述条件时,必须调整颜色并旋转树形。
在这里插入图片描述
插入节点如下:
在这里插入图片描述
四种状态分析:以伯父节点分情况讨论
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
状况4父子节点皆为红色的情况持续向上调整,处理时效不佳,可以施行一个由上而下的程序:假设新增节点为A,延着A的路径,只要看到有某节点X的两子节点皆为红色,就把X改为红色,并把两子节点改为黑色,持续向上修改。
在这里插入图片描述
状况3、4综合为插入节点A的父节点P为红色,S也为红色时,P的父节点为黑色,修改P、S为黑色,修改P的父节点为红色,持续向上,当A的父节点P为红色,S为黑色时,如上图X的父节点P为红色,S为黑色,此时需像状况1做一次单旋转并改变颜色,或像状况2做一次双旋转并改变颜色。
再直接插入节点35,或插入后再一次单旋转。
在这里插入图片描述

施行由上而下程序代码如下:

//左旋
/*
左旋转,旋转点的右子节点旋转上去:
旋转点为右子节点的左子节点,安置右子节点的左子节点,
右子节点继承旋转点的父节点关系。

x为旋转点,y为旋转点x的右子节点,
则x的右子节点为y的左子节点,y的左子节点的父节点为x,
y的父节点为x的父节点,父节点的子节点设置为y,
最后设置x,y的父子节点关系。

在处理节点关系时,因节点之间关系是双向的,设置好父子的关系。
*/
void __rb_tree_rotate_left(__rb_tree_node_base* x, __rb_tree_node_base*& root) //x为旋转点
{
  __rb_tree_node_base* y = x->right; //令y为旋转点x的右子节点
  x->right = y->left; //x的右子节点为y的左子节点
  if (y->left != 0) //如果y的左子节点不为空
    y->left->parent = x; //设置y的左子节点的父节点为x
  y->parent = x->parent; //y的父节点为x的父节点
  //y需要将x父节点的所有关系接收过来
  if (x == root) //x为根节点时
    root = y;
  else if (x == x->parent->left) //x为其父节点的左子节点
    x->parent->left = y;
  else //x为其父节点的右子节点
    x->parent->right = y;
  y->left = x; //设置y节点的子节点
  x->parent = y; //设置x节点的父节点
}

//右旋
/*
右旋转,将旋转点的左子节点旋转上去:
旋转点为左子节点的右子节点,安置左子节点的右子节点,
左子节点继承旋转点的父节点关系。

x为旋转点,y为旋转点x的左子节点,
则y的右子节点为x的左子节点,x的左子节点的父节点为y,
y的父节点为x的父节点,父节点的子节点设置为y,
最后设置x,y的父子节点关系。
*/
void __rb_tree_rotate_right(__rb_tree_node_base* x, __rb_tree_node_base*& root) //x为旋转点
{
  __rb_tree_node_base* y = x->left; //令y为旋转点x的左子节点
  x->left = y->right; //x的左子节点为y的右子节点
  if (y->right != 0) //如果y的右子节点不为空
    y->right->parent = x; //设置y的右子节点的父节点为x
  y->parent = x->parent; //y的父节点为x的父节点
  //y需要完全顶替x,必须将x父节点的关系完全接收
  if (x == root) //x为根节点
    root = y;
  else if (x == x->parent->right) //x为其父节点的右子节点
    x->parent->right = y;
  else
    x->parent->left = y; //x为其父节点的左子节点
  y->right = x; //y的右子节点为x
  x->parent = y; //x的父节点为y
}

//树形调整,旋转树形及调整颜色
void __rb_tree_rebalance(__rb_tree_node_base* x, __rb_tree_node_base*& root) //x为新增节点,root为根节点
{
  x->color = __rb_tree_red; //新节点必为红
  while (x != root && x->parent->color == __rb_tree_red) //循环条件,x不为根节点且父节点为红
  {
    if (x->parent == x->parent->parent->left) //父节点为祖父节点的左子节点
    {
      __rb_tree_node_base* y = x->parent->parent->right; //令y为伯父节点
      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; //令x等于祖父节点,继续循环
      }
      else //无伯父节点,或伯父节点为黑
      {
        if (x == x->parent->right) //x为父节点的右子节点
        { //父节点为祖父节点左子节点,x节点为父节点的右子节点,内侧插入先单旋至外侧
          x = x->parent; //x等于父节点,将父节点左旋转,x为旋转下来的父节点
          __rb_tree_rotate_left(x, root); //旋转后x依然为外侧插入点
        } 
        //右旋转,x节点和祖父节点旋转至父节点的左右子节点,先改变颜色再旋转
        x->parent->color = __rb_tree_black; //父节点旋转后依然是父节点颜色为黑
        x->parent->parent->color = __rb_tree_red; //祖父节点旋转后为x节点兄弟节点,颜色为红,x节点为红不需要改变
        __rb_tree_rotate_right(x->parent->parent, root); //祖父节点作旋转点右旋转,旋转后x节点位置未改变,树形调整基本完成
      }
    }
    else //父节点为祖父节点的右节点
    {
      __rb_tree_node_base* y = x->parent->parent->left; //令y为伯父节点
      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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值