红黑树

红黑树的概念

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

红黑树的性质

1.每个结点不是红色就是黑色
2.根节点是黑色的
3.如果一个节点是红色的,则它的两个孩子结点是黑色的
4.对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
5.每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?
假如最短路径上的结点颜色全部为黑色,如果要形成一条最长路径,根据第3,4条规定则最长路径结点是红黑交替,且黑色结点和最短路径黑色结点数量相同,所以最长路径中节点个数不会超过最短路径节点个数的两倍。

红黑树节点的定义

enum { BLACK, RED };

template<typename K, typename V>
struct RBTreeNode
{
	int _color;
	K _key;
	V _value;
	RBTreeNode<K, V> *_left;
	RBTreeNode<K, V> *_right;
	RBTreeNode<K, V> *_parent;
	RBTreeNode(K key, V value)
		:_color(RED)               //默认节点是红色
		, _key(key)
		, _value(value)
		, _left(NULL)
		, _right(NULL)
		, _parent(NULL)
	{}
};

为什么默认节点是红色?
因为插入之前所有根至外部节点的路径上黑色节点数目都相同,所以如果插入的节点是黑色肯定错误(黑色节点数目不相同),而相对的插入红节点可能不会违反“没有两个连续两个节点是红色”这一条件,所以插入的节点为红色,如果违反条件再调整。

红黑树的旋转

红黑树的左旋:
动态图:
在这里插入图片描述
在这里插入图片描述
旋转过程:
以y为中心,将y的父节点x,左边兄弟节点α逆时针旋转,
原来的y左节点β被y的父节点替代,
原来y的左子节点β逆时针方向平移后变成y的原父节点x的右子节点。

void RotateL(Node* parent)         //左旋
	{
		Node* ppNode = parent->_parent;
		Node* subR = parent->_right;

		parent->_right = subR->_left;
		if (subR->_left)
			subR->_left->_parent = parent;

		subR->_left = parent;
		parent->_parent = subR;

		if (ppNode == NULL)
		{
			_root = subR;
			_root->_parent = NULL;
		}
		else
		{         //与上层进行链接
			if (ppNode->_left == parent)
				ppNode->_left = subR;
			else
				ppNode->_right = subR;
			subR->_parent = ppNode;
		}
	}

红黑树的右旋:
动态图:
在这里插入图片描述
在这里插入图片描述
右旋过程:
将x的父节点y,右边兄弟节点λ,还有右子节点β顺时针旋转。
原来的x右节点β被x的父节点替代,
原来的x的右子节点β顺时针方向平移后变成x的原父节点y的左子节点。

void RotateR(Node* parent)          //右旋
	{
		Node* ppNode = parent->_parent;
		Node* subL = parent->_left;

		parent->_left = subL->_right;
		if (subL->_right)
			subL->_right->_parent = parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (ppNode == NULL)
		{
			_root = subL;
			_root->_parent = NULL;
		}
		else                           //与上层结点链接
		{
			if (ppNode->_left == parent)
				ppNode->_left = subL;
			else
				ppNode->_right = subL;
			subL->_parent = ppNode;
		}
	}

红黑树的插入

新节点默认颜色为红色
1.如果插入的节点是根节点,则直接插入,并且将根节点染成黑色。
2.如果要插入的位置的父亲节点是黑色的,那么直接插入。
3.要插入的位置的父亲是红色的,这时候再插入一个红色节点就会出现两个红色节点梁旭的情况,所以要进行调整。假设要插入的节点是cur,它的父亲是parent,它父亲的兄弟是uncle,它的祖父是grand。
情况一:p为红,cur为红,g为黑,u存在且为红
在这里插入图片描述
因为cur和p均为红,违反了性质3,所以需要调整。
解决方法,将p,cur改为黑,g改为红,然后把g当成cur,继续向上调整。

情况二:cur为红,p为红,g为黑,u不存在/u为黑
在这里插入图片描述
p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反, p为g的右孩子,cur为p的右孩子,则进行左单旋转
p、g变色–p变黑,g变红

情况三:cur为红,p为红,g为黑,u不存在/u为黑
在这里插入图片描述
p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反, p为g的右孩子,cur为p的左孩子,则针对p做右单旋转
则转换成了情况2

bool Insert(const K& key, const V& value)
	{
		if (_root == NULL)    //如果插入的结点是根节点
		{
			_root = new Node(key, value);
			_root->_color = BLACK;
			return true;
		}
		Node* cur = _root;
		Node* parent = NULL;
		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>key)
			parent->_left = cur;
		else
			parent->_right = cur;
		cur->_parent = parent;

		while (cur != _root&&parent->_color == RED)      //父节点是红色的需要调整
		{
			Node* grand = parent->_parent;      //找到祖父结点
			if (parent == grand->_left)
			{
				Node* uncle = grand->_right;     //找到叔叔结点
				if (uncle&&uncle->_color == RED)      //叔叔结点是红色
				{
					grand->_color = RED;
					parent->_color = BLACK;
					uncle->_color = BLACK;
					cur = grand;
					parent = cur->_parent;      //红色结点上移,需要继续判断
				}
				else            //叔叔结点不存在或为黑色结点
				{
					//先处理双旋的情况
					if (cur == parent->_right)    //如果cur是父亲的右孩子
					{
						RotateL(parent);         //先对parent进行左旋
						parent = cur;
					}

					//如果cur是parent的右孩子,则经过旋转之后现在就变成了以grand右旋的情况
					RotateR(grand);        //对祖父结点进行右旋
					parent->_color = BLACK;
					grand->_color = RED;
					break;      //这时候就已经平衡了
				}
			}
			else
			{
				Node* uncle = grand->_left;
				if (uncle&&uncle->_color == RED)     //如果叔叔存在且为红色
				{
					parent->_color = BLACK;
					uncle->_color = BLACK;
					grand->_color = RED;        //红色结点上移,继续向上判断
					cur = grand;
					parent = cur->_parent;
				}
				else
				{
					//如果cur是parent的左孩子,则需要先进行右旋将双旋转换成左旋的情况
					if (cur == parent->_left)
					{
						RotateR(parent);
						parent = cur;
					}

					//在对祖父进行左旋
					RotateL(grand);
					parent->_color = BLACK;
					grand->_color = RED;
					break;
				}
			}
		}
		_root->_color = BLACK;     //把根节点置成黑色
		return true;
	}

红黑树的验证

1.检测其是否满足二叉搜索树(中序遍历是否为有序序列)
2.检测其是否满足红黑树的性质

bool IsValidRBTree()
{
     Node*  pRoot = GetRoot();

     // 空树也是红黑树 
     if (nullptr == pRoot)
      return true;
```// 检测根节点是否满足情况
     if (BLACK != pRoot->_color)
     {
         cout << "违反红黑树性质二:根节点必须为黑色" << endl; 
         return false;
     }

     // 获取任意一条路径中黑色节点的个数 
     size_t blackCount = 0;
     Node* pCur = pRoot;
     while (pCur)
     {
         if (BLACK == pCur->_color) 
             blackCount++;

         pCur = pCur->_left; 
     }

     // 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数 
     size_t k = 0;
     return _IsValidRBTree(pRoot, k, blackCount);
 }

bool _IsValidRBTree(PNode pRoot, size_t k, const size_t blackCount)
{
     if (nullptr == pRoot) 
         return true;

     // 统计黑色节点的个数
     if (BLACK == pRoot->_color) 
         k++;

     // 检测当前节点与其双亲是否都为红色 
     Node* pParent = pRoot->_pParent; 
     if (pParent && RED == pParent->_color && RED == pRoot->_color)
     {
         cout << "违反性质三:没有连在一起的红色节点" << endl; 
         return false;
     }

     // 如果pRoot是因子节点,检测当前路径中黑色节点的个数是否有问题
     if (nullptr == pRoot->_left && nullptr == pRoot->_rght)
     {
         if (k != blackCount)
         {
             cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl; 
             return false;
         }
     }

     return _IsValidRBTree(pRoot->_left, k, blackCount) &&
     _IsValidRBTree(pRoot->_right, k, blackCount);

红黑树与AVL树的比较

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值