二叉搜索树的平衡处理——红黑树

文章介绍了红黑树作为一种近似平衡的二叉搜索树,其主要性质包括节点颜色、根节点颜色等,并对比了红黑树与AVL树的区别。在实现部分,详细阐述了红黑树节点的插入过程,包括不同情况下需要进行的旋转和变色策略,以保持树的平衡。

一、红黑树的概念

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

红黑树和AVL树的区别:

红黑树:最长路径不超过最短路径的两倍(近似平衡)

AVL树:左右子树高度差不大于一(严格平衡)

相对而言,插入同样的数据,AVL树旋转的更多,红黑树则旋转的少。

二、红黑树的性质

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

2.根节点是黑色的

3.如果一个节点是红色的,那么它的两个孩子节点是黑色的(树中没有连续的红色节点

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

5.每个叶子节点(又称为NIL节点)都是黑色的(此处叶子节点指的是空节点

三、红黑树的实现

红黑树节点的实现

//红黑树节点的实现
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)
		,_kv(kv)
		,_col(RED)//初始化为红色,因为每次插入都是默认为红色插入的。
	{}

};

插入的过程和AVL树一样都是三叉链插入, 所以后续会用到旋转 ,但是对于红黑树来说它还需要变色:插入的节点默认为红色的,如果插入的节点的父亲为黑色,那么插入没问题,如果插入的节点的父亲为红色 那就和上面红黑树的性质冲突,此时就需要变色了

 红黑树的插入操作

cur -->当前位置节点

parent-->当前位置的父节点

grandparent-->parent的父节点

uncle -->parent的兄弟节点

情况1:cur为红,parent为红,grandparent为黑,uncle存在且为红

 a/b/c/d/e 为符合红黑树规则的子树 ,若a/b/c/d/e为空树 ,则cur为新增

处理:parent和uncle变黑,grand变红,继续向上处理,grandparent若为根则再变为黑。

注意:只变色处理不考虑左右插入。

情况二:cur为红,parent为红,grandparent为黑,uncle存在且为黑/uncle不存在

uncle 不存在的情况就是abcde为空 cur为新增 


 

 处理:把p变黑,g变红,右旋

单旋+变色

情况三:cur为红,p为红,g为黑,u存在且为黑/u不存在,和情况二样,p为g的左孩子,cur为p的右孩子,则针对p左旋。变为上面的情况。

 

总结:看uncle ,uncle为红色就变色,继续往上更新; uncle不存在或者为黑色,就需要变色并旋转:

cur的位置 \旋转+变色
cur在左左右旋       p->黑 u->黑 g->红
左右先p左旋 再右旋cur->黑 g->红
右右左旋p->黑 u->黑 g->红
右左先p右旋 再左旋c->黑 g->红

代码实现:

template <class K,class V>
class RBTree
{
	typedef RBTreeNode<K,V> Node;
	typedef RBTreeNode<K,V>* PNode;

public:
	RBTree()
		:_root(nullptr)
	{}

	bool Insert(pair<K, V>& kv)
	{
	    // 1、搜索树的规则插入
		// 2、看是否违反平衡规则,如果违反就需要处理:旋转
		
		//判断是否为空树 是空树直接插入黑色节点作为根节点
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}

		PNode parent = nullptr;//记录下父节点以便后续插入链接
		PNode cur = _root;//当前位置--即插入位置节点
		
		while (cur)
		{
			//左子树小 右子树大 寻找插入位置
			if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;//去重
			}
		}

		cur = new Node(kv);//插入
		cur->_col = RED;//插入节点默认为红色

		//判断插入节点是父亲的左还是右儿子 再和父节点链接
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		// 存在连续红色节点 此时与红黑树的性质三相悖 需要处理 
		while (parent && parent->_col == RED)
		{
			PNode grandfater = parent->_parent;//grandparent节点
			assert(grandfater);//防御

			if (grandfater->_left == parent)
			{
				PNode uncle = grandfater->_right;//找出uncle节点 再根据uncle的状态来分情况讨论
				// 情况一:
				if (uncle && uncle->_col == RED) // 叔叔存在且为红 此时变色即可解决不需要旋转 变色处理不考虑左右
				{
					// 变色
					parent->_col = uncle->_col = BLACK;//parent和uncle变黑 g变红 
					grandfater->_col = RED;

					// 继续往上处理 直到根节点为止
					cur = grandfater;
					parent = cur->_parent;
				}
				else // 叔叔不存在 或者 叔叔存在且为黑
				{
					if (cur == parent->_left) // 左左  --  p变黑 g变红 并且右单旋
					{
						RotateR(grandfater);
						parent->_col = BLACK;
						grandfater->_col = RED;
					}
					else // 左右 -- p左旋 g右旋并且变色
 					{
						RotateL(parent);
						RotateR(grandfater);
						cur->_col = BLACK;
						grandfater->_col = RED;
					}
					break;
				}
			}
			else //(grandfater->_right == parent)
			{
				Node* uncle = grandfater->_left;
				// 情况一:
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;

					// 继续往上处理
					cur = grandfater;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_right)//右右 左单选并变色 :p变黑 g变红
					{
						RotateL(grandfater);
						parent->_col = BLACK;
						grandfater->_col = RED;
					}
					else // 双旋 右左 -- p右旋 g左旋 并且 变色
					{
						RotateR(parent);
						RotateL(grandfater);
						cur->_col = BLACK;
						grandfater->_col = RED;
					}
					break;
				}
			}
		}

		_root->_col = BLACK;//记住根节点要变为黑色 符合性质1
		return true;
	}
private:
	PNode _root;
};

其中的左右旋转 和AVL树中的左右旋转是一样的 我们再温故一波:

void RotateR(PNode parent)
	{
		PNode subL = parent->_left;
		PNode subLR = subL->_right;
		PNode ppNode = parent->_parent;//注意旋转完成之后新的根节点要和原来根节点的父节点链接		
		parent->_left = subLR;
		subL->_right = parent;
		parent->_parent = subL;
		if (subLR)
		{
			subLR->_parent = parent;
		}
		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
				ppNode->_left = subL;
			else
				ppNode->_right = subL;
			subL->_parent = ppNode;
		}
	}

	void RotateL(PNode parent)
	{
		PNode subR = parent->_right;
		PNode subRL = subR->_left;
		PNode ppNode = parent->_parent;
		parent->_right = subRL;
		subR->_left = parent;
		parent->_parent = subR;
		if (subRL)
		{
			subRL->_parent = parent;
		}
		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
				ppNode->_left = subR;
			else
				ppNode->_right = subR;
			subR->_parent = ppNode;
		}
	}

1本程序在vc++6.0编译通过并能正常运行。 2主界面 程序已经尽量做到操作简便了,用户只需要根据提示一步步进行操作就行了。 六思考和总结: 这个课程设计的各个基本操作大部分都在我的综合性实验中实现了,所以做这个主要攻克插入和删除这两个算法!其中插入在书本上已经有了,其中的右平衡算法虽然没有给出,但通过给出的左平衡算法很容易就可以写出右平衡算法。所以最终的点就在于删除算法的实现!做的过程中对插入算法进行了非常非常多次的尝试!花了非常多的时间,这其中很多时候是在对程序进行单步调试,运用了VC6。0的众多良好工具,也学到了很多它的许多好的调试手段。 其中删除算法中最难想到的一点是:在用叶子结点代替要删除的非叶子结点后,应该递归的运用删除算法去删除叶子结点!这就是整个算法的核心,其中很强烈得体会到的递归的强大,递归的最高境界(我暂时能看到的境界)! 其它的都没什么了。选做的那两个算法很容易实现的: 1合并两棵平衡二叉排序树:只需遍历其中的一棵,将它的每一个元素插入到另一棵即可。 2拆分两棵平衡二叉排序树:只需以根结点为中心,左子树独立为一棵,右子树独立为一棵,最后将根插入到左子树或右子树即可。 BSTreeEmpty(BSTree T) 初始条件:平衡二叉排序树存在。 操作结果:若T为空平衡二叉排序树,则返回TRUE,否则FALSE. BSTreeDepth(BSTree T) 初始条件:平衡二叉排序树存在。 操作结果:返回T的深度。 LeafNum(BSTree T) 求叶子结点数,非递归中序遍历 NodeNum(BSTree T) 求结点数,非递归中序遍历 DestoryBSTree(BSTree *T) 后序遍历销毁平衡二叉排序树T R_Rotate(BSTree *p) 对以*p为根的平衡二叉排序树作右旋处理处理之后p指向新的树根结点 即旋转处理之前的左子树的根结点 L_Rotate(BSTree *p) 对以*p为根的平衡二叉排序树作左旋处理处理之后p指向新的树根结点, 即旋转处理之前的右子树的根结点 LeftBalance(BSTree *T) 对以指针T所指结点为根的平衡二叉排序树作左平衡旋转处理, 本算法结束时,指针T指向新的根结点 RightBalance(BSTree *T) 对以指针T所指结点为根的平衡二叉排序树作右平衡旋转处理, 本算法结束时,指针T指向新的根结点 Insert_AVL(BSTree *T, TElemType e, int *taller) 若在平衡二叉排序树T中不存在和e有相同的关键字的结点, 则插入一个数据元素为e的新结点,并返回OK,否则返回ERROR. 若因插入而使二叉排序树失去平衡,则作平衡旋转处理 布尔变量taller反映T长高与否 InOrderTraverse(BSTree T) 递归中序遍历输出平衡二叉排序树 SearchBST(BSTree T, TElemType e, BSTree *f, BSTree *p) 在根指针T所指的平衡二叉排序树中递归的查找其元素值等于e的数据元素, 若查找成功,则指针p指向该数据元素结点,并返回TRUE,否则指针p 指向查找路径上访问的最后一个结点并返回FALSE,指针f指向T的双亲, 其初始调用值为NULL Delete_AVL(BSTree *T, TElemType e, int *shorter) 在平衡二叉排序树中删除元素值为e的结点,成功返回OK,失败返回ERROR PrintBSTree_GList(BSTree T) 以广义表形式打印出来 PrintBSTree_AoList(BSTree T, int length) 以凹入表形式打印,length初始值为0 Combine_Two_AVL(BSTree *T1, BSTree T2) 合并两棵平衡二叉排序树 Split_AVL(BSTree T, BSTree *T1, BSTree *T2) 拆分两棵平衡二叉树 } (2)存储结构的定义: typedef struct BSTNode { TElemType data; int bf; //结点的平衡因子 struct BSTNode *lchild, *rchild;//左.右孩子指针 }BSTNode, *BSTree;
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值