红黑树简析

一.   概念

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


 二.   性质

1. 每个结点不是红色就是黑色

2. 根节点是黑色的

3. 如果一个节点是红色的,则它的两个孩子结点是黑色的

4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点

5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

由于从一个节点起的每条路径中的黑色节点数目相同,因此最短为全黑,同时由于红节点不连续,因此最长为一半红一半黑,因此不会超出两倍


三.   节点定义

三叉链,key-value模型、额外的标明颜色的变量(颜色可以使用枚举常量)

同时,为了在插入时遵循第4条准则,我们将颜色变量的默认值设为红色

enum Colour
{
	RED,
	BLACK
};

template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;

	Colour _col;

	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(RED)
		, _kv(kv)
	{}
};

四.   基本框架

template<class K, class V>
struct RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	RBTree()
		:_root(nullptr)
	{}
private:
		Node* _root;
};

五.   插入

首先,一开始与AVL树是一致的,都是通过比较key来寻找合适的位置进行插入

bool Insert(const pair<K, V>& kv)
{
	if (!_root)
	{
		_root = new Node(kv);
		_root->_col = BLACK;
		return false;
	}
	Node* parent = nullptr;
	Node* cur = _root;

	while (cur)
	{
		if (cur->_kv.first == kv.first)
			return false;
		else if (cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			parent = cur;
			cur = cur->_left;
		}
	}

	cur = new Node(kv);
	if (parent->_kv.first < kv.first)
		parent->_right = cur;
	else
		parent->_left = cur;
	cur->_parent = parent;
}

之后,虽然我们插入红色节点会使得每条路径的黑节点数目相同,但还要注意在红节点连续时进行相应的调整

在调整时,依旧是从插入节点(cur)向上迭代来观察,而我们需要进行调整的情况为cur和parent的颜色都为红,主要分为这两种情况

同样,和AVL树一样,也有对称的情况,但操作方法也都是类似的,不多说了

而这两种情况,我们对子树4的情况进行细分,首先当节点1的右节点为红色时

 处理方法都是一样的,只需要将节点2、4的颜色改为黑色,同时,我们在改变结构时,为了减小对上面的影响,应该保证前后的路径中黑节点的数目相同,改色之前,黑节点数目为n+1,因此,我们还需要将节点1改为红色

而由于我们将节点1的颜色改为了红色,我们需要继续向上判断

while (parent&&parent->_col == RED)
{
	Node* par_parent = parent->_parent;
	Node* uncle = nullptr;
	if (parent == par_parent->_left)
		uncle = par_parent->_right;
	else
		uncle = par_parent->_left;

	if (uncle && uncle->_col == RED)
	{
		uncle->_col = BLACK;
		parent->_col = BLACK;
		par_parent->_col = RED;
		cur = par_parent;
		parent = cur->_parent;
	}
}

 

还有两种情况,是节点4不存在或节点4为黑色,这两种情况的处理情况相同,由于不需要对节点4进行处理,我们就简化一下

首先是情况1

 首先若是我们想要改变颜色,首先对于节点2,右侧路径黑节点为n个,因此左侧也应该为n个,所以节点3的颜色不能改变,因此我们只能改变节点2,这样对于节点1,两侧路径的黑节点数目就不可能相同了,因此,我们可以使用在AVL树中使用到的旋转,很容易看出,我们需要使用右旋(对称情况使用左旋)

 之后,便可以来改变颜色,首先,有两种方案,节点3改为黑色或节点1改为红色、节点2改为黑色,原本路径中黑节点数为n+1,这两种方案改变后都符合,但由于若是我们将节点2颜色改为黑色,与改变前类似,也就不需要继续向上判断了,因此采用第二种方法

else
{
	if (cur == parent->_left && parent == par_parent->_left)
	{
		RotateR(par_parent);
		parent->_col = BLACK;
		par_parent->_col = RED;
	}
	else if (cur == parent->_right && parent == par_parent->_right)
	{
		RotateL(par_parent);
		parent->_col = BLACK;
		par_parent->_col = RED;
	}
	break;
}

而左旋和右旋和AVL树是一样的 

void RotateL(Node* parent)
{
	Node* par_parent = parent->_parent;
	Node* right = parent->_right;
	Node* right_left = right->_left;
	if (par_parent)
	{
		if (parent == par_parent->_left)
			par_parent->_left = right;
		else
			par_parent->_right = right;
	}
	else
	{
		_root = right;
	}
	right->_parent = par_parent;
	if (right_left)
		right_left->_parent = parent;
	parent->_right = right_left;
	right->_left = parent;
	parent->_parent = right;
}

void RotateR(Node* parent)
{
	Node* par_parent = parent->_parent;
	Node* left = parent->_left;
	Node* left_right = left->_right;
	if (par_parent)
	{
		if (parent == par_parent->_left)
			par_parent->_left = left;
		else
			par_parent->_right = left;
	}
	else
	{
		_root = left;
	}
	left->_parent = par_parent;
	if (left_right)
		left_right->_parent = parent;
	parent->_left = left_right;
	left->_right = parent;
	parent->_parent = left;
}

 

之后便是情况2

同样只是改变颜色无法实现平衡 

我们就需要进行双旋

之后就需要改变颜色

还是将节点3变黑,节点1变红,不需要向上调整

else
{
	if (cur == parent->_left && parent == par_parent->_left)
	{
		RotateR(par_parent);
		parent->_col = BLACK;
		par_parent->_col = RED;
	}
	else if (cur == parent->_left && parent == par_parent->_right)
	{
		RotateR(parent);
		RotateL(par_parent);
		cur->_col = BLACK;
		par_parent->_col = RED;
	}
	else if (cur == parent->_right && parent == par_parent->_right)
	{
		RotateL(par_parent);
		parent->_col = BLACK;
		par_parent->_col = RED;
	}
	else
	{
		RotateL(parent);
		RotateR(par_parent);
		cur->_col = BLACK;
		par_parent->_col = RED;
	}
	break;
}

而在最后,头结点可能会被我们改变为红色,我们只需要将其手动变为黑就好了,不会对下面产生影响

总览

bool Insert(const pair<K, V>& kv)
{
	if (!_root)
	{
		_root = new Node(kv);
		_root->_col = BLACK;
		return false;
	}
	Node* parent = nullptr;
	Node* cur = _root;

	while (cur)
	{
		if (cur->_kv.first == kv.first)
			return false;
		else if (cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			parent = cur;
			cur = cur->_left;
		}
	}

	cur = new Node(kv);
	if (parent->_kv.first < kv.first)
		parent->_right = cur;
	else
		parent->_left = cur;
	cur->_parent = parent;

	while (parent&&parent->_col == RED)
	{
		Node* par_parent = parent->_parent;
		Node* uncle = nullptr;
		if (parent == par_parent->_left)
			uncle = par_parent->_right;
		else
			uncle = par_parent->_left;

		if (uncle && uncle->_col == RED)
		{
			uncle->_col = BLACK;
			parent->_col = BLACK;
			par_parent->_col = RED;
			cur = par_parent;
			parent = cur->_parent;
		}
		else
		{
			if (cur == parent->_left && parent == par_parent->_left)
			{
				RotateR(par_parent);
				parent->_col = BLACK;
				par_parent->_col = RED;
			}
			else if (cur == parent->_left && parent == par_parent->_right)
			{
				RotateR(parent);
				RotateL(par_parent);
				cur->_col = BLACK;
				par_parent->_col = RED;
			}
			else if (cur == parent->_right && parent == par_parent->_right)
			{
				RotateL(par_parent);
				parent->_col = BLACK;
				par_parent->_col = RED;
			}
			else
			{
				RotateL(parent);
				RotateR(par_parent);
				cur->_col = BLACK;
				par_parent->_col = RED;
			}
			break;
		}
	}
	_root->_col = BLACK;
	return true;
}

void RotateL(Node* parent)
{
	Node* par_parent = parent->_parent;
	Node* right = parent->_right;
	Node* right_left = right->_left;
	if (par_parent)
	{
		if (parent == par_parent->_left)
			par_parent->_left = right;
		else
			par_parent->_right = right;
	}
	else
	{
		_root = right;
	}
	right->_parent = par_parent;
	if (right_left)
		right_left->_parent = parent;
	parent->_right = right_left;
	right->_left = parent;
	parent->_parent = right;
}

void RotateR(Node* parent)
{
	Node* par_parent = parent->_parent;
	Node* left = parent->_left;
	Node* left_right = left->_right;
	if (par_parent)
	{
		if (parent == par_parent->_left)
			par_parent->_left = left;
		else
			par_parent->_right = left;
	}
	else
	{
		_root = left;
	}
	left->_parent = par_parent;
	if (left_right)
		left_right->_parent = parent;
	parent->_left = left_right;
	left->_right = parent;
	parent->_parent = left;
}

六.   验证 

首先就是验证是否为搜索树,和AVL树的一致

void InOrder()
{
	_InOrder(_root);
	cout << endl;
}

void _InOrder(Node* root)
{
	if (root == NULL)
		return;

	_InOrder(root->_left);
	cout << root->_kv.first << ":" << root->_kv.second <<':' << root->_col << "  ";
	_InOrder(root->_right);
}

之后,还需要验证三个问题:根节点是否为黑,路径中的黑节点数目是否相同、红节点是否不连续

首先第一点很好验证,若是验证第二点,我们可以先得到一条路径的黑节点,之后以它为基准,对每条路径进行判断,最后在遍历路径的时候顺便对红节点是否连续做一下判断

bool IsBalance()
{
	if (_root && _root->_col == RED)
	{
		cout << "根节点不是黑色" << endl;
		return false;
	}

	int banchmark = 0;
	Node* left = _root;
	while (left)
	{
		if (left->_col == BLACK)
			banchmark++;
		left = left->_left;
	}
	int blackNum = 0;
	return _IsBalance(_root, banchmark, blackNum);
}

bool _IsBalance(Node* root, int& banchmark, int blackNum)
{
	if (!root)
	{
		if (banchmark != blackNum)
		{
			cout << "存在路径黑色节点的数量不相等" << endl;
			return false;
		}
		return true;
	}
	if (root->_col == RED && root->_parent->_col == RED)
	{
		cout << "出现连续红色节点" << endl;
		return false;
	}

	if (root->_col == BLACK)
	{
		++blackNum;
	}

	return _IsBalance(root->_left, banchmark, blackNum)
		&& _IsBalance(root->_right, banchmark, blackNum);
}

end


 


 

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

finish_speech

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值