数据结构——红黑树

简介: 数据结构——红黑树

概念与性质

概念:

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。

性质:

  1. 每个结点不是红色就是黑色 。
  2. 根节点是黑色的 。
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的。(这里的意思是两个红色节点不能连接到一起)
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。(这里的意思是每条路径的黑色节点数形同)
  5. 每个叶子结点都是黑色的。(此处的叶子结点指的是空结点,也可以叫做NIL结点)

为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?

我们来考虑最好和最坏的情况:

最好:全都是黑节点,这是满二叉树。时间复杂度:O(logN)

最坏:最不平衡,但是又不破坏性质,也就是比最好情况多找一倍的量,2 logN,这个量对于计算机而言是非常容易的。时间复杂度:O(logN)

对于这两种情况,如果其中一条路径添加黑结点,那么其他路径也要添加黑结点,如果是最坏的情况22号结点添加了一个红色结点是不允许的,所以只要遵循红黑树的性质就不会右最长路径超过最短路径二倍的问题。

这棵树和AVL树不同的是没有平衡因子,多了一个颜色变量。

相比较于AVL树,红黑树的旋转更少一些,AVL总是要旋转,也是会影响效率的。

树节点的定义

enum Color//利用枚举来给红黑树配色
{
  RED,
  BLACK
};
template<class K, class V>
struct RBTreeNode
{
  RBTreeNode(const T& data)
    :_data(data)
    , _color(RED)//这里一定要给红色,如果给黑色其他路径就要涉及到也要加黑色结点,更加麻烦
    ,_left(nullptr)
    ,_right(nullptr)
    ,_parent(nullptr)
  {}
  RBTreeNode* _left;
  RBTreeNode* _right;
  RBTreeNode* _parent;
  pair<K, V> _data;
  Color _color;//结点的配色
};

插入

插入的第一步和搜索二叉树的一样,先找到合适的位置。

第二步就是判断是否符合红黑树的性质,因为插入的新节点是红色的,所以分为以下几个情况考虑。

图中的g代表grandfather,p是parent,u是uncle,c是cur。

情况一: cur为红,p为红,g为黑,u存在且为红。

c为新增节点,p为红色,u也为红色,g点为黑色,这个时候应该将p和u变成黑色,g变成红色。

第一种情况,无论c插入的地方是p的子树还是u的子树都无所谓。

这里还要注意,如果g结点的上面还是红色结点,这里就需要继续向上调整。

这个时候就要将g赋值给c,p等于c的parent指向的结点。

情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑。

首先对g右单旋,然后将p变为红色,g变为黑色。

这个情况无论是对于局部树还是根节点都不会使上面的发生任何变化,因为原来g结点的位置变成了p结点,但是颜色还是黑色。

这里就算u结点什么也没有,也是按照旋转+变色的方式来解决问题。

情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑

p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;

相反,p为g的右孩子,cur为p的左孩子,则针对p做右单旋转

则转换成了情况2

其实这三种情况看下来之后我们发现,最重要的是叔叔节点。

代码实现

bool Insert(const pair<K, V>& data)
  {
    if (_root == nullptr)
    {
      _root = new Node(data);
      _root->_color = BLACK;
      return true;
    }
    Node* cur = _root;
    Node* parent = nullptr;
    while (cur)
    {
      if (cur->_data.first > data.first)
      {
        parent = cur;
        cur = cur->_left;
      }
      else if (cur->_data.first < data.first)
      {
        parent = cur;
        cur = cur->_right;
      }
      else
      {
        return false;
      }
    }
    cur = new Node(data);//这里默认是红色结点
    if (parent->_data.first > data.first)
    {
      cur->_parent = parent;
      parent->_left = cur;
    }
    else if (parent->_data.first < data.first)
    {
      cur->_parent = parent;
      parent->_right = cur;
    }
    //如果父节点为空就代表cur是根节点,没必要调整了
    //还要判断cur结点是否与父节点的颜色均为红色
    while (parent && parent->_color == RED)
    {
      Node* grandfather = parent->_parent;//祖父结点
      if (parent == grandfather->_left)//新增结点在祖父左
      {
        Node* uncle = grandfather->_right;
        //情况一
        if (uncle && uncle->_color == RED)//这里不要忘记验证uncle的存在
        {
          parent->_color = BLACK;
          uncle->_color = BLACK;
          grandfather->_color = RED;
          cur = grandfather;//最后让cur等于祖父结点的位置
          parent = cur->_parent;
        }
        else
        {
          if (parent->_left == cur)//情况二
          {
            RotateR(grandfather);//右单旋
            grandfather->_color = RED;
            parent->_color = BLACK;
          }
          else if (parent->_right == cur)//情况三
          {
            RotateL(parent);//左单旋
            RotateR(grandfather);//右单旋
            cur->_color = BLACK;
            grandfather->_color = RED;
          }
          break;//第二种和第三种情况变完之后因为最上面的组节点变为黑,所以这里跳出循环
        }
      }
      else//新增结点在祖父右
      {
        Node* uncle = grandfather->_left;
        if (uncle && uncle->_color == RED)//情况一
        {
          uncle->_color = BLACK;
          parent->_color = BLACK;
          grandfather->_color = RED;
          cur = grandfather;
          parent = cur->_parent;
        }
        else {
          if (cur == parent->_right)//情况二
          {
            RotateL(grandfather);
            grandfather->_color = RED;
            parent->_color = BLACK;
          }
          else if (cur == parent->_left)//情况三
          {
            RotateR(parent);
            RotateL(grandfather);
            cur->_color = BLACK;
            grandfather->_color = RED;
          }
          break;
        }
      }
    }
    _root->_color = BLACK;
    return true;
  }
  void RotateL(Node* parent)//左单旋
  {
    Node* pparent = parent->_parent;
    Node* subR = parent->_right;
    Node* subRL = subR->_left;
    parent->_right = subRL;
    if (subRL)
      subRL->_parent = parent;
    subR->_left = parent;
    parent->_parent = subR;
    if (pparent)
    {
      if (pparent->_left == parent)
        pparent->_left = subR;
      else
        pparent->_right = subR;
    }
    else
    {
      _root = subR;
    }
    subR->_parent = pparent;
  }
  void RotateR(Node* parent)//右单旋
  {
    Node* pparent = parent->_parent;
    Node* subL = parent->_left;
    Node* subLR = subL->_right;
    parent->_left = subLR;
    if (subLR)
      subLR->_parent = parent;
    subL->_right = parent;
    parent->_parent = subL;
    if (pparent)
    {
      if (pparent->_left == parent)
        pparent->_left = subL;
      else
        pparent->_right = subL;
    }
    else
    {
      _root = subL;
    }
    subL->_parent = pparent;
  }

红黑树的验证

红黑树的验证需要去检验不能是两个连续的红色结点,并且每条路径上的黑色节点相同。

这里解决的方法是添加两个参数,一个参数用来计算每条路径上的黑色结点,另外一个值用来储存某一条路径的黑色结点的个数。

bool _IsBalanceTree(Node* root, int k, int sum)//验证
  {
    if (root == nullptr)
    {
      if (k == sum)//这里代表当前路径点和最左边的路径点相同
        return true;
      else
      {
        cout << "每条路径上黑色结点数量不同" << endl;
      }
      return false;
    }
    if (root->_color == BLACK)
      k++;
    if (root->_parent && root->_parent->_color == RED && root->_color == RED)
    {
      cout << root->_parent->_data.first << endl;
      return false;
    }
    return _IsBalanceTree(root->_left, k, sum)&&_IsBalanceTree(root->_right, k, sum);
  }

红黑树与AVL树的对比

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(l o g 2 N log_2 Nlog2N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中黑树更多。

相关文章
|
8月前
|
算法 C++
【数据结构与算法】—— 手撕红黑树
【数据结构与算法】—— 手撕红黑树
|
8月前
|
存储 关系型数据库 数据库
【数据结构】—红黑树(C++实现)
【数据结构】—红黑树(C++实现)
|
关系型数据库
|
5月前
|
存储 算法 C语言
"揭秘C语言中的王者之树——红黑树:一场数据结构与算法的华丽舞蹈,让你的程序效率飙升,直击性能巅峰!"
【8月更文挑战第20天】红黑树是自平衡二叉查找树,通过旋转和重着色保持平衡,确保高效执行插入、删除和查找操作,时间复杂度为O(log n)。本文介绍红黑树的基本属性、存储结构及其C语言实现。红黑树遵循五项基本规则以保持平衡状态。在C语言中,节点包含数据、颜色、父节点和子节点指针。文章提供了一个示例代码框架,用于创建节点、插入节点并执行必要的修复操作以维护红黑树的特性。
119 1
【数据结构】红黑树——领略天才的想法
【数据结构】红黑树——领略天才的想法
|
3月前
|
Java C++
【数据结构】探索红黑树的奥秘:自平衡原理图解及与二叉查找树的比较
本文深入解析红黑树的自平衡原理,介绍其五大原则,并通过图解和代码示例展示其内部机制。同时,对比红黑树与二叉查找树的性能差异,帮助读者更好地理解这两种数据结构的特点和应用场景。
44 0
|
8月前
【数据结构】红黑树的原理及其实现
【数据结构】红黑树的原理及其实现
|
5月前
|
存储 Java 程序员
"揭秘HashMap底层实现:从数组到链表,再到红黑树,掌握高效数据结构的秘密武器!"
【8月更文挑战第21天】HashMap是Java中重要的数据结构,采用数组+链表/红黑树实现,确保高效查询与更新。构造方法初始化数组,默认容量16,负载因子0.75触发扩容。`put`操作通过计算`hashCode`定位元素,利用链表或红黑树处理冲突。`get`和`remove`操作类似地定位并返回或移除元素。JDK 1.8优化了链表转红黑树机制,提升性能。理解这些原理能帮助我们更高效地应用HashMap。
53 0
|
7月前
|
算法 架构师 NoSQL
【数据结构之红黑树】深入原理与实现
意节点的左子树和右子树的层高差不大于1,为了维护树的平衡,我们介绍了树的左右旋转。但是,AVL树维护平衡的代价是比较大的。所以,我们又介绍了红黑树这种数据结构,这是因为红黑树插入的效率相对AVL树是比较高的,在统计意义上来讲红黑树在插入和查找综合上效率是比较高的,这也是为什么红黑树为什么广泛应用在计算机各个方面。
69 2
|
7月前
|
C++
数据结构===红黑树
数据结构===红黑树

热门文章

最新文章