红黑树的简单实现

目录

1.红黑树的概念

2.红黑树的性质

3.红黑树节点的定义

4.红黑树的插入

4.1 正常插入

4.2 判断是否需要旋转

4.2.1.uncle存在且颜色为红

4.2.2 uncle存在且颜色为黑

4.2.3 uncle不存在


1.红黑树的概念

我们使用二叉搜索树时,它可能退化为单支树或近似单支树,这时候它的搜索效率就退化到O(n),与AVL树一样,红黑树的出现就是来解决这种情况,红黑树是一种二叉搜索树。它保证二叉搜索树搜索效率的根本逻辑就是将每个节点增加了颜色属性,并制定了一套规则,根据这个规则的特性来判断是否需要旋转,以此维持平衡。

2.红黑树的性质

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

2.根结点为黑色。

3.如果一个节点为红色,那它的子节点必须为黑色。

4.对于每个节点,从该节点开始到它的每个叶节点的路径上的黑色节点数相同。

5.每个叶节点为黑色(这里的叶节点指的是空节点)。

规则5我们是最后统计路径用的,我们暂时不需要管。

规则1、2、3、4保证:最短路径的节点数*2>=最长路径的节点数。原因如下:

假设最短路径的黑色节点数为n,那么最长路径的黑色节点数也为n(规则4),此时最长路径的红色节点数最多为n(规则3,即黑红黑红……黑红),不算最短路径中红色节点的情况下,有:

最短路径的节点数*2==最长路径的节点数

如果加上最短路径中红色节点,有:

最短路径的节点数*2>=最长路径的节点数

这样红黑树就近似平衡。

3.红黑树节点的定义

enum color
{
	RAD,
	BLACK
};
template<class T>
struct RBTreeNode
{
	RBTreeNode(const T& data = T())
		: _pLeft(nullptr)
		, _pRight(nullptr)
		, _pParent(nullptr)
		, _data(data)
		, _color(RAD)
	{}
	RBTreeNode<T>* _pLeft;
	RBTreeNode<T>* _pRight;
	RBTreeNode<T>* _pParent;
	T _data;
	color _color;
};

4.红黑树的插入

4.1 正常插入

首先按照正常二叉搜索树进行插入,为了后续对map,set的封装,我这里增加了头结点,头结点的左孩子为红黑树的最小节点,右孩子为红黑树的最大节点,父节点为根节点,令根节点的父节点为头结点。

bool Insert(const T& data)
{
	if (Find(data) == nullptr)
	{
		Node* newnode = new Node(data);
		if (_pHead->_pParent == nullptr)
		{
			_pHead->_pParent = newnode;
			_pHead->_pLeft = newnode;
			_pHead->_pRight = newnode;
			newnode->_pParent = _pHead;
		}
		else
		{
			Node* parent = nullptr, * cur = _pHead->_pParent;
			while (cur)
			{
				parent = cur;
				if (data < cur->_data) cur = cur->_pLeft;
				else if (data > cur->_data) cur = cur->_pRight;
			}
			cur = newnode;
			if (newnode->_data < _pHead->_pLeft->_data) _pHead->_pLeft = newnode;
			if (newnode->_data > _pHead->_pRight->_data) _pHead->_pRight = newnode;
            //更新头节点的左右孩子
            if (data > parent->_data) parent->_pRight = newnode;
			else if (data < parent->_data) parent->_pLeft = newnode;
			newnode->_pParent = parent;
            //判断是否需要旋转
            //…………
			
		}
		_pHead->_pParent->_color = BLACK;
		return true;
	}
	return false;
}

4.2 判断是否需要旋转

然后就是判断是否需要旋转:

我们令新插入的节点默认为红色,如果插入后它的父节点也为红色,那么就破坏了红黑树的规则,我们需要改变颜色或者进行旋转来维持红黑树的性质,我们这里先引入几个称呼:

child为新插入节点,parent为插入节点的父节点,grandparent为parent的父节点,uncle为grandparent的另一个子节点,我们根据uncle是否存在以及uncle的颜色来划分出需要旋转的情况。

4.2.1.uncle存在且颜色为红

这种情况不需要旋转,只需将parent与uncle改为黑色,grandparent改为红色即可。这里无论child为左孩子还是右孩子处理方式都是一样的。

注意:这里可能将根节点改为红色,需要特殊处理。

4.2.2 uncle存在且颜色为黑

正常情况下不会直接出现这种情况,这种情况只会是有通过第一种情况改变颜色后出现,如:

我们这里只考虑蓝色部分:

注意:下图省略了部分子树(参考上图),最后的结果是符合红黑树的性质的。

这种情况先对child进行左旋,再对parent进行右旋,最后将child改为黑色,grandparent改为黑色即可。类似的,如果parent为右孩子,child为左孩子,就先对child进行右旋,再对parent进行左旋,最后改色。

如果child与parent均为各自父节点的左孩子:

直接对parent进行右旋即可,将parent改为黑色,grandparent改为红色。类似的,如果child与parent均为各自父节点的右孩子,进行左旋,最后改色。

4.2.3 uncle不存在

这种情况处理方式同4.2.2,先对child进行左旋,再对parent进行右旋,最后将child改为黑色,grandparent改为黑色即可。类似的,如果parent为右孩子,child为左孩子,就先对child进行右旋,再对parent进行左旋,最后改色。

如果child与parent均为各自父节点的左孩子:

直接对parent进行右旋即可,将parent改为黑色,grandparent改为红色。类似的,如果child与parent均为各自父节点的右孩子,进行左旋,最后改色。

代码如下:

while (parent->_color == RAD && parent != parent->_pParent->_pParent)
{
				Node* grandparent = parent->_pParent;
				Node* uncle = parent == grandparent->_pLeft ? grandparent->_pRight : grandparent->_pLeft;
                //如果uncle为红色
				if (uncle && uncle->_color == RAD)
				{
					parent->_color = uncle->_color = BLACK;
					grandparent->_color = RAD;
                    //进行迭代
					parent = grandparent->_pParent;
					cur = grandparent;
				}
                //uncle不存在或者uncle为黑色
				else
				{
                    //parent为左孩子
					if (parent == grandparent->_pLeft)
					{
                        //child为左孩子,右旋
						if (cur == parent->_pLeft)
						{
							RotateR(grandparent);
							parent->_color = BLACK;
							grandparent->_color = RAD;
						}
                        //child为右孩子,先左旋再右旋
						else
						{
							RotateL(parent);
							RotateR(grandparent);
							cur->_color = BLACK;
							grandparent->_color = RAD;
						}
					}
                    //parent为右孩子
					else
					{
                        //child为右孩子,左旋
						if (cur == parent->_pRight)
						{
							RotateL(grandparent);
							parent->_color = BLACK;
							grandparent->_color = RAD;
						}
                        //child为左孩子,先右旋再左旋
						else
						{
							RotateR(parent);
							RotateL(grandparent);
							cur->_color = BLACK;
							grandparent->_color = RAD;
						}
					}
					break;
				}
}

旋转部分代码:

// 左单旋
void RotateL(Node* pParent)
{
	Node* subR = pParent->_pRight;
	Node* subRL = subR->_pLeft;
	Node* ppParent = pParent->_pParent;
	pParent->_pRight = subRL;
	if (subRL) subRL->_pParent = pParent;
	pParent->_pParent = subR;
	subR->_pLeft = pParent;
	if (pParent == _pHead->_pParent)
	{
		_pHead->_pParent = subR;
		subR->_pParent = _pHead;
	}
	else
	{
		if (pParent == ppParent->_pLeft) ppParent->_pLeft = subR;
		else if (pParent == ppParent->_pRight) ppParent->_pRight = subR;
		subR->_pParent = ppParent;
	}
}
// 右单旋
void RotateR(Node* pParent)
{
	Node* subL = pParent->_pLeft;
	Node* subLR = subL->_pRight;
	Node* ppParent = pParent->_pParent;
	pParent->_pLeft = subLR;
	if (subLR) subLR->_pParent = pParent;
	pParent->_pParent = subL;
	subL->_pRight = pParent;
	if (pParent == _pHead->_pParent)
	{
		_pHead->_pParent = subL;
		subL->_pParent = _pHead;
	}
	else
	{
		if (pParent == ppParent->_pLeft) ppParent->_pLeft = subL;
		else if (pParent == ppParent->_pRight) ppParent->_pRight = subL;
		subL->_pParent = ppParent;
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值