【数据结构】红黑树(如何实现及怎样判断)

      红黑树
是一颗二叉搜索树,它在每个节点上增加了一个存储位来表示节点的颜色,可以是red或black。通过对任何一条从根节点到叶子节点的简单路径上的颜色来约束,红黑树保证了最长路径不超过最短路经的两倍,因此近似于平衡

红黑树的规则:

1、每个节点不是红色就是黑色的。

2、根结点是黑色的。

3、如果一个节点是红色的,则它的两个子结点是黑色的。即每条路径上不能存在两个连续的红节点。

4、对每个节点,从该节点到其他节点的简单路径上,均包含相同数目的黑色节点

红黑树节点RBTreeNode的实现,利用三叉链(left、right、parent)、key、value及颜色col。

enum colour
{
	RED,
	BLACK,
};
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	K _key;
	V _value;
	colour _col;
	RBTreeNode(const K& key = K(), const V& value = V())
		:_left(NULL)
		, _right(NULL)
		, _parent(NULL)
		, _key(key)
		, _value(value)
		, _col(RED)//初始化插入节点的颜色为红色,不影响黑节点个数
	{}
};

一、红黑树的插入

      红黑树的插入类似于二叉搜索树,但是每次插入后都到要注意是否满足红黑树的规则,特别是规则3和规则4。如果不满足就需要调整树的结构,下面对插入节点时分成以下几种情况:

注:cur(插入节点)parent(cur的父亲节点)grandfather(parent的父亲节点)uncle(父亲的兄弟节点)

1、根节点root为空,直接插入新节点并给root,设置根节点的颜色为BLACK。

2、根节点root不为空,找到插入节点的位置并插入节点cur。cur节点是红色,若parent是红节点,则需要进行调整。

      存在以下三种情况:

      情况一:cur为红,parent为红,grandfather为黑,uncle存在且为红。

     调整方案,如下如所示:

    《【数据结构】红黑树(如何实现及怎样判断)》

      情况二:cur为红,parent为红,grandfather为黑,uncle不存在或者uncle为黑。

                     左单旋:parent为grantfather的右孩子,cur为parent的右孩子。

                     右单旋: parent为grantfather的左孩子,cur为parent的左孩子。

      调整方案,下面以右单旋进行分析,如下图所示:

      《【数据结构】红黑树(如何实现及怎样判断)》

       左单旋转类似右单旋转的实现过程。

      情况三:cur为红,parent为红,grantfather为黑,uncle不存在或者uncle为黑。 

                    左右单旋:parent为grantfather的右孩子,cur为parent的左孩子。

                    右左单旋:parent为grantfather的左孩子,cur为parent的右孩子。

      调整方案,下面以做右单旋进行分析,如下图所示:

     《【数据结构】红黑树(如何实现及怎样判断)》

      右左旋转的实现类似左单旋转的实现过程。

具体实现如下:

RBTree()
 :_root(NULL)
{}
bool Insert(const K& key, const V& value)
{
 //1、_root为空时,插入节点是根节点
 if (_root == NULL)
 {
  _root = new Node(key, value);
  _root->_col = BLACK;
  return true;
 }
 Node* parent = NULL;
 Node* cur = _root;
 while (cur)//找到插入节点的位置
 {
  if (cur->_key > key)
  {
   parent = cur;
   cur = cur->_left;
  }
  else if (cur->_key < key)
  {
   parent = cur;
   cur = cur->_right;
  }
  else
   return false;
 }
 cur = new Node(key, value);//插入节点
 if (parent->_key > cur->_key)
 {
  parent->_left = cur;
  cur->_parent = parent;
 }
 if (parent->_key < cur->_key)
 {
  parent->_right = cur;
  cur->_parent = parent;
 }
 //2、出现两个连续红节点,进行调整
 while (cur != _root && parent->_col == RED)//此条件说明存在parent节点,parent存在父亲节点
 {
  Node* grandfather = parent->_parent;
  if (parent == grandfather->_left)
  {
   Node* uncle = grandfather->_right;
   if (uncle && uncle->_col == RED)//情况一:uncle为RED
   {
    grandfather->_col = RED;
    parent->_col = BLACK;
    uncle->_col = BLACK;
   }
   else //情况二、三:uncle为BLACK或不存在(右单旋或左右单旋)
   {//先考虑左右单旋,先进行左单旋,转化为情况二,再进行右单旋
    if (cur == parent->_right)
    {
     _RotateL(parent);//左旋不需要变颜色
    }
    _RotateR(grandfather);
   }
  }
  else
  {
   Node* grandfather = parent->_parent;
   if (parent == grandfather->_right)
   {
    Node* uncle = grandfather->_left;
    if (uncle && uncle->_col == RED)//情况一:uncle为RED
    {
     grandfather->_col = RED;
     parent->_col = BLACK;
     uncle->_col = BLACK;
    }
    else //情况二、三:uncle为BLACK或不存在(左单旋或右左单旋)
    {
     if (cur == parent->_left)
     {
      _RotateR(parent);//右旋不需要变颜色
     }
     _RotateL(grandfather);
    }
   }
  }
  cur = grandfather;
  parent = cur->_parent;
  _root->_col = BLACK;
 }
}
void _RotateL(Node* parent)
{
 Node* SubR = parent->_right;
 Node* SubRL = SubR->_left;
 parent->_right = SubRL;
 if (SubRL)
  SubRL->_parent = parent;
 SubR->_parent = parent->_parent;
 SubR->_left = parent;
 parent->_parent = SubR;
 //变色
 SubR->_col = BLACK;//情况二中:parent变黑,grandfather变红
 parent->_col = RED;
 parent = SubR;
 if (parent->_parent == NULL)
  _root = parent;
 else
 {
  Node* ppNode = parent->_parent;
  if (ppNode->_key > parent->_key)
   ppNode->_left = parent;
  else
   ppNode->_right = parent;
 }
}
void _RotateR(Node* parent)
{
 Node* SubL = parent->_left;
 Node* SubLR = SubL->_right;
 parent->_left = SubLR;
 if (SubLR)
  SubLR->_parent = parent;
 SubL->_parent = parent->_parent;
 SubL->_right = parent;
 parent->_parent = SubL;
 //变色
 SubL->_col = BLACK;//情况二中:parent变黑,grandfather变红
 parent->_col = RED;
 parent = SubL;
 if (parent->_parent == NULL)
  _root = parent;
 else
 {
  Node* ppNode = parent->_parent;
  if (ppNode->_key > parent->_key)
   ppNode->_left = parent;
  else
   ppNode->_right = parent;
 }
}

二、红黑树的判断

1、根结点是否满足红黑树规则,是否为黑色。

2、每条路径的黑色节点相等。统计出一条路径的黑色节点的个数,然后与其他路径黑色节点个数进行比较。

3、不存在连续的红色节点,判断红色节点的父亲节点是否为红色。

具体实现如下:

bool Check()
{
	if (_root->_col == RED)
		return false;
	int count = 0;//统计出一条路径的黑色节点的个数
	int num = 0;//需要与count比较的其他路径黑色节点个数
	Node* cur = _root;
	while (cur)
	{
		if (cur->_col == BLACK)
			count++;
		cur = cur->_left;
	}
	return _Check(_root, count, num);
}
bool _Check(Node* root, int BlackNum, int CurBlackNum)
{
 if (root == NULL)
  return true;
 if (root->_col == RED && root->_parent->_col == RED)//存在两个连续的红节点
  return false;
 if (root->_col == BLACK)//黑节点就CurBlackNum++
  CurBlackNum++;
 if (root->_left == NULL && root->_right == NULL)
 {
  if (CurBlackNum == BlackNum)
   return true;
  else//黑色节点不相等返回false
   return false;
 }
 return _Check(root->_left, BlackNum, CurBlackNum) 
  && _Check(root->_right, BlackNum, CurBlackNum);//进行左右递归
}

红黑树的效率:

1、最坏情况下,红黑树高度不超过2lgN

最坏的情况就是,红黑树中除了最左侧路径全部是由3-node节点组成,即红黑相间的路径长度是全黑路径长度的2倍

2、红黑树的平均高度大约为lgN

红黑树的运用(高效的二叉搜索树)

红黑树这种数据结构应用十分广泛,在多种编程语言中被用作符号表的实现,如:

红黑树和AVL树的比较

1、红黑树和AVL树都是高效的平衡二叉树,增删查改的时间复杂度都是O(lg(N))

2、红黑树的不追求完全平衡,保证最长路径不超过最短路径的2倍,相对而言,降低了旋转的要求,所以性能优于AVL树,所以实际运用中红黑树更多。红黑树是一种特殊的二叉查找树,他的查找方法也和二叉查找树一样,不需要做太多更改,但是由于红黑树比一般的二叉查找树具有更好的平衡,所以查找起来更快。

以上为个人学习的一些总结,如有纰漏,请多多指教。



    原文作者:算法小白
    原文地址: https://blog.csdn.net/Scenlyf/article/details/51692551
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞