C++实现AVL树和红黑树(插入部分)

news2024/10/7 6:42:12

文章目录

  • 前言
  • 1.AVL树的相关介绍
  • 2.AVL树的旋转
    • 1.失衡状态
    • 2.旋转调整
  • 3.代码实AVL树
    • 1.节点的插入
    • 2.插入部分的验证
  • 4.红黑树的相关介绍
  • 5.红黑树的插入调整
    • 1.处理方式
    • 2.代码实现
  • 6.红黑树的检查
  • 7.总结

前言

之前介绍了二叉搜索树,本文主要是对AVL树和红黑树进行介绍。普通的二叉搜索树插入结点之后可能会失去平衡,退化成单链表形式造成查找效率低下,因此引入了二叉搜索树和红黑树,在插入节点后会进行一定的处理维持树的平衡性,提高查找效率。本文主要介绍的是插入节点后处理,对删除节点没有做介绍。


1.AVL树的相关介绍

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年
发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

AVL树的特性

对任何一个节点来说它的左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)的二叉搜索树就是AVL树,空树也可以看出AVL树。

当在AVL树中插入节点后,可能会因为造成失衡,也就是平衡因子可能会超过2.这个时候就需要旋转处理,因此来维持AVL树的特性,AVL树一旦失衡也就不再是AVL树了。AVL树最精华核心的地方就是旋转。

2.AVL树的旋转

1.失衡状态

AVL树的旋转是插入节点造成AVL树的平衡因子大于2之后一种维持树的平衡性的操作,造成失衡的情况有4种情况,分别是LL型,RR型,LR型,RL型这4种状态失衡。具体情况我们看图分析。

在这里插入图片描述

在这里插入图片描述

对于一颗平衡的AVL树来说插入节点会影响到从当前节点到根节点路径上的平衡因子,也就是说它会造成某一条路径上的节点的子树失衡甚至影响到根节点的子树的高度差失衡。这个失衡状态是从下往上影响的,因为AVL树要求左右子树高度差不超过1,可能原来某个节点的子树高度差是1插入节点成了0,更加平衡了,这个时候一定能保证这个该节点开始往后的所有路径上节点的子树都是平衡的,就不用管了。但是也可能某个节点子树高度差不满足这个条件了。针对失衡状态引入上述模型图,当插入节点后,插入路径上的某个节点的子树的失衡了,不外乎上述4种情况。该子树路径中左孩子的左子树失衡,这是LL型,右孩子的右子树失衡这是RR型,左孩子的右子树失衡这是LR型,右孩子的左子树失衡这是RL型。

2.旋转调整

在AVL树中出现失衡状态就会旋转处理,根据失衡状态的不同旋转处理的方式。LL是右单旋,RR是左单旋,LR是左右双旋,RL是右左双旋。具体的旋过程我们来看图。

在这里插入图片描述

关于旋转这块只能画图理解,至于为什么非得这么旋转,这里由于我才疏学浅给不出证明,大家可自行研究,但是可以保证的是当插入路径中某个节点的子树不平衡后,通过上述的旋转一定能保证AVL树的平衡性。上述理论部分完了之后,接下来就是写代码了。我们来看看代码应该怎么写。

3.代码实AVL树

AVL树本质也是二叉树搜索树,这里选用KV模型的二叉搜索树来实现AVL树。

节点的定义

template <class K ,class V>
struct AVLTreeNode 
{
	AVLTreeNode <K, V>*_left;
	AVLTreeNode <K, V>*_right;
	AVLTreeNode <K, V>* _parent;
	pair<K, V> _kv;
	int _bf;
	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		 ,_parent(nullptr)
		 ,_bf(0)
		,_kv(kv)
	{
		;
	}
};

AVL树每个节点中需要一个字段bf记录当前节点左右子树的高度差。这里我将bf定义为右子树高度减去左子树的高度。当然这只是我的定义方式,大家也可以将bf定义为左子树的高度减去右子树高度。因为每次插入节点该插入节点路径的上的节点左右子树的bf都会被更新。这里定义parent指针指向每个节点的父节点。这样方便自底向上调整bf和旋转处理。根节点的parent是空,因为根节点没有父节点。

1.节点的插入

节点插入开始的时候就很普通二叉搜索树一样,比较大小插入进树中最后连接上。但是连接上后需要更新平衡因子,当平衡因子大于1或者小于-1的时候就需要调整了。

bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}
		Node* parent = nullptr;
		Node* 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);
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;
		//更新平衡因子
		while (parent)
		{
			if (cur == parent->_right)
			{
				parent->_bf++;
			}
			else
			{
				parent->_bf--;
			}
			if (parent->_bf == 1 || parent->_bf == -1)
			{
				// 继续更新
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (parent->_bf == 0)
			{
				break;
			}
			//旋转处理
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
			    //LL型右旋
				if (parent->_bf == -2 && cur->_bf == -1)
				{
					RotateR(parent);
				}
				//RR型左旋
				else if(parent->_bf == 2 && cur->_bf == 1)
				{
					RotateL(parent);
				}
				//LR型 左右双旋
				else if (parent->_bf == -2&&cur->_bf==1)
				{    
					RotateLR(parent);
				}
				//RL型 右左双旋
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				else
				{
					assert(false);
				}

				break;
			}
			else
			{
				cout << "Insert assert" << endl;
				assert(false);
			}
		}
		return true;
	}

因为有个_parents指针,当cur节点和对应的父节点连接上后,cur的_parent指向也要指向对应的父节点进行更新。之后就是更新因子,因为我们定义bf是右子树的高度减去左子树的高度,如果cur是parent的右节点,parent的bf++,反之bf- -,因为该插入路径上节点的bf会被影响,因此需要自底向上不断更新bf。如果更新后某个节点的bf是为0的,就不用继续向上更新即可,直接跳出循环。如果bf2或者bf-2的时候就需要旋转处理了。如果bf大于2或者bf小于-3那说明之前的更新就有问题了。在代码中写个断言,这样确保我们程序出错后快速定位问题,同时也可提早发现程序问题。这是一个不错的coding技巧。假如某次更新后parens出现bf为2或者-2,该如何确定应该用那种方式处理呢?

更新后parent的bf为2或者为-2时,进行调整的时候需要根据先前的图来确定处理方式。LL型是parents的左子树中出现问题,且是左孩子的左子树的出现问题。对应的就是parents的bf==-2&&cur的bf==-1,为什么?我们定义的是bf==右子树减去左子树。当parent的左树出现问题时,肯定是左树高对应也就是bf==2,cur又是parent的左孩子,那么cur的左树也是较高的那一方,对应的就是cur==-1.同理我们也可以推倒出其他的类型情况,当parent的bf==-2 && cur的bf == 1 时肯定就是LR型,当parent的bf == 2 且cur 的bf==-1时肯定就是RL型,当parent的bf2,且cur1肯定就是RR型。这些都是结合图以及左右子树的高度差分析出来的,并不是凭空想象的。先前的图很重要,需要细细品味。

知道了在哪种情况下应该用哪种处理方式,接下来就是实现旋转了

右单旋

void RotateR(Node* parent)
	{
		Node* child = parent->_left;
		Node* ppnode = parent->_parent;
		parent->_left = child->_right;
		if (child->_right)
		{
			child->_right->_parent = parent;
		}

		child->_right = parent;
		parent->_parent = child;

		if (parent == _root)
		{
			_root = child;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = child;
			}
			else
			{
				ppnode->_right =child;
			}
			child->_parent = ppnode;
		}

		child->_bf = parent->_bf = 0;
	}

这里右旋的操作还是先看看之前的图,我们这里右单旋的parent的左孩子的左子树出现了问题,这里先把左孩子用变量保存起来,从之前的图中的我们可以看到左孩子节点的右孩子部分是连接在上parent的左指针上的。这个时候需要改变child的右孩子的_parent的指向,但是这个child的右孩子可能会是空,这个时候需要先判断一下child的右孩子是否为空在考虑更新_parent的指向。同样的parent的_parent的指向也需要更新,更新为child节点。同时这个parent的也将成为chil的右孩子,这个时候child的_parent的指向也需要更新,在将这个child和之前的parent的双亲节点进行链接。如果之前parent的双亲节点是_root需要单独处理一下。以上链接部分可能文字叙述比较绕我建议还是对着图来链接。

这里简单理解总结一下链接步骤。child的右孩子成为parent的左孩子,parent成为chid的右孩子。这个时候需要更新3个节点的双亲节点的指针,child ,child的右孩子,parent的双亲节点都应该更新。这个child的右孩子可能为空,这个需要单独处理。之后需要将之前parent的双亲节点和child建立连接关系。这样算是连接成功,之后最后一步,就是更新平衡因子,这里只有child的平衡因子和parent的平衡因子的需要更新,且更新为0.为什么呢?再来看看图。

在这里插入图片描述

总的来说右旋之后parent和child的bf一定为0。接下来我们来看看左旋操作。

左单旋

//左单旋操作
	void RotateL(Node* parent)
	{
		Node* ppnode = parent->_parent;
		Node* child = parent->_right;

		parent->_right = child->_left;
		if (child->_left)
		{
			child->_left->_parent = parent;
		}
		child->_left = parent;
		parent->_parent = child;

		if (ppnode == nullptr)
		{
			_root = child;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = child;
			}
			else
			{
				ppnode->_right = child;
			}

			child->_parent = ppnode;
		}

		parent->_bf = child->_bf = 0;
	}

这里左旋的时候child是parent的右孩子,先把parent的双亲节点保存起来,之后child和建立新的链接关系的时候需要用到。之后和右旋一样,链接新的指向关系,更新双亲节点的指向。parent和child的平衡因子都得更新成为0.这些指向关系以及平衡因子的更新都得看先前的那张图。其实这里文字叙述越多反而越乱。

接下来看看双旋,左右双旋就是一次左旋,一次右旋,右左双旋就是一次右旋,一次左旋。我们直接复用代码即可,主要需要搞清楚平衡因子的更新。

//左右双旋
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;

		RotateL(parent->_left);
		RotateR(parent);
		if (bf == 1)
		{
			parent->_bf = 0;
			subLR->_bf = 0;
			subL->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			subLR->_bf = 0;
			subL->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subLR->_bf = 0;
			subL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

这里左右双旋,先左旋parent的左孩子,在右旋parent的右孩子。这点对着图来看,我们谈及一下这个平衡因子的更新。

在这里插入图片描述

这个bf的更新就是取决于child的右孩子的bf,这里我们最好还是对着图来分析写代码。因为旋转之后,节点会移动,所以在旋转之前保存一下对应的节点,这样才能正确的更新节点的bf。这样我们先来看看这个右左双旋。

右旋双旋

void RotateRL(Node* parent)
	{
		Node* child = parent->_right;
		Node* subRL = child->_left;
		int bf = child->_left->_bf;

		RotateR(parent->_right);
		RotateL(parent);

		if (bf == 1)
		{
			child->_bf = 0;
			parent->_bf = -1;
			subRL->_bf=0;
		}
		else if (bf == -1)
		{
			child->_bf = 1;
			parent->_bf = 0;
			subRL->_bf = 0;
		}
		else if (bf == 0)
		{
			child->_bf = 0;
			parent->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

右左双旋,就是先右旋parent的右孩子,在左旋parent.接着还是平衡因子的更新,平衡因子的更新和左右双旋类似,我们画图分析一下。

在这里插入图片描述

其实上述图都是模型图,就相当于将插入节点后,插入路径中的某个失衡节点以及其左右子树单独拿出来分析。这样便于分析,以上的代码细节都可以从图中找到答案。

这里最后总结一下:左旋RR失衡,需要parent和child以及chid的左孩子,这里的child是parent的右孩子。右旋LL失衡,需要parent和child以及child的右孩子,这里child是parent的左孩子。左右双旋是LR型,需要左旋parent的左孩子,再右旋parent。右左双旋RL型,右旋parent的右孩子,再左旋parent。

2.插入部分的验证

当我们实现好了插入部分,需要验证一下结果是否正确。这里需要写两个函数,一个判断树是否是平衡的,还有一个就是求树高。因为判断树是否平衡需要用到树高。

int _Height(Node* root)
	{
		if (root == NULL)
			return 0;

		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);

		return leftH > rightH ? leftH + 1 : rightH + 1;
	}
	int Height()
	{
		return _Height(_root);
	}
	bool _IsBalance(Node* root)
	{
		if (root == NULL)
		{
			return true;
		}

		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);

		if (rightH - leftH != root->_bf)
		{
			cout << root->_kv.first << "节点平衡因子异常" << endl;
			return false;
		}

		return abs(leftH - rightH) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}
	bool IsBalance()
	{
		return _IsBalance(_root);
	}

在判断树是否平衡这里,多加了一个判断条件,我们的平衡因子更新是否出错。如果平衡因子更新出错,这是属于隐藏的bug,不容易发现。这样可以提前排查问题,确定程序的正确性。

完整代码

#include<assert.h>
#include<map>
#include<iostream>
using namespace std;
template <class K ,class V>
struct AVLTreeNode 
{
	AVLTreeNode <K, V>*_left;
	AVLTreeNode <K, V>*_right;
	AVLTreeNode <K, V>* _parent;
	pair<K, V> _kv;
	int _bf;
	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		 ,_parent(nullptr)
		 ,_bf(0)
		,_kv(kv)
	{
		;
	}
};

template <class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V>Node;
public:

	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}
		Node* parent = nullptr;
		Node* 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);
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;
		//更新平衡因子
		while (parent)
		{
			if (cur == parent->_right)
			{
				parent->_bf++;
			}
			else
			{
				parent->_bf--;
			}
			if (parent->_bf == 1 || parent->_bf == -1)
			{
				// 继续更新
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (parent->_bf == 0)
			{
				break;
			}
			//旋转处理
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
			    //LL型右旋
				if (parent->_bf == -2 && cur->_bf == -1)
				{
					RotateR(parent);
				}
				//RR型左旋
				else if(parent->_bf == 2 && cur->_bf == 1)
				{
					RotateL(parent);
				}
				//LR型 左右双旋
				else if (parent->_bf == -2&&cur->_bf==1)
				{    
					RotateLR(parent);
				}
				//RL型 右左双旋
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				else
				{
					assert(false);
				}

				break;
			}
			else
			{
				cout << "Insert assert" << endl;
				assert(false);
			}
		}
		return true;
	}
	//左单旋操作
	void RotateL(Node* parent)
	{
		Node* ppnode = parent->_parent;
		Node* child = parent->_right;

		parent->_right = child->_left;
		if (child->_left)
		{
			child->_left->_parent = parent;
		}
		child->_left = parent;
		parent->_parent = child;

		if (ppnode == nullptr)
		{
			_root = child;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = child;
			}
			else
			{
				ppnode->_right = child;
			}

			child->_parent = ppnode;
		}

		parent->_bf = child->_bf = 0;
	}

	//右单旋操作
	void RotateR(Node* parent)
	{
		Node* child = parent->_left;
		Node* ppnode = parent->_parent;
		parent->_left = child->_right;
		if (child->_right)
		{
			child->_right->_parent = parent;
		}

		child->_right = parent;
		parent->_parent = child;

		if (parent == _root)
		{
			_root = child;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = child;
			}
			else
			{
				ppnode->_right =child;
			}
			child->_parent = ppnode;
		}

		child->_bf = parent->_bf = 0;
	}
	//左右双旋
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;

		RotateL(parent->_left);
		RotateR(parent);
		if (bf == 1)
		{
			parent->_bf = 0;
			subLR->_bf = 0;
			subL->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			subLR->_bf = 0;
			subL->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subLR->_bf = 0;
			subL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}
	void RotateRL(Node* parent)
	{
		Node* child = parent->_right;
		Node* subRL = child->_left;
		int bf = child->_left->_bf;

		RotateR(parent->_right);
		RotateL(parent);

		if (bf == 1)
		{
			child->_bf = 0;
			parent->_bf = -1;
			subRL->_bf=0;
		}
		else if (bf == -1)
		{
			child->_bf = 1;
			parent->_bf = 0;
			subRL->_bf = 0;
		}
		else if (bf == 0)
		{
			child->_bf = 0;
			parent->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	void Inorder()
	{
		_Inorder(_root);
	}
	void _Inorder(Node*root)
	{
		if (root == nullptr)
		{
			return;
		}
		_Inorder(root->_left);
		cout << root->_kv.first <<" ";
		_Inorder(root->_right);
	}
	int _Height(Node* root)
	{
		if (root == NULL)
			return 0;

		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);

		return leftH > rightH ? leftH + 1 : rightH + 1;
	}

	bool _IsBalance(Node* root)
	{
		if (root == NULL)
		{
			return true;
		}

		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);

		if (rightH - leftH != root->_bf)
		{
			cout << root->_kv.first << "节点平衡因子异常" << endl;
			return false;
		}

		return abs(leftH - rightH) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}
	bool IsBalance()
	{
		return _IsBalance(_root);
	}

	int Height()
	{
		return _Height(_root);
	}
private:

	Node* _root=nullptr;
};

测试代码

#include"AVLTree.h"
void Test_AVLTree1()
{
	
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	AVLTree<int, int> t1;

	for (auto e : a)
	{
		t1.Insert(make_pair(e, e));
		cout << e << "插入:" << t1.IsBalance() << endl;
	}

	t1.Inorder();
	cout << t1.IsBalance() << endl;
}

void Test_AVLTree2()
{
	srand(time(0));
	const size_t N = 9999999;
	AVLTree<int, int> t;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand() + i;
		t.Insert(make_pair(x, x));
	}
	cout << t.IsBalance() << endl;
	cout << t.Height() << endl;
}
int main()
{
	Test_AVLTree1();
	Test_AVLTree2();
}

4.红黑树的相关介绍

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。之所以会有红黑树,是因为AVL树插入节点后需要频繁的旋转调整,这样会影响效率,由此才有了红黑树这种这种折中的方案,红黑树在保证查找效率的同时,减少了节点旋转调整从次数,从而提高效率。

红黑树的相关特性

1.个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

其实红黑树的几条特性主要就保证红黑树中这样的的特性:红黑树中不能出现出现连续红色节点,红黑树的中每个节点到其所以后袋的简单路径上黑色节点的数目是相同的。通过这样的条件限制后,红黑树中最长的路径是全是黑色,最短的路径一半黑一半红。假设最短路径节点有n个节点,那么最长路径就是2*n个节点。那么单次查找效率在logN到log2N之间基本上还是logN的时间复杂度,同时也没有那么多旋转处理了,红黑树整体效率大大提高。

在这里插入图片描述

如图就是一颗红黑树,红黑树中的叶子节点一般是指空节点,而不是像之前二叉树中度为0的节点。

5.红黑树的插入调整

1.处理方式

和AVL树一样,红黑树也是需要调整的,当插入节点是遇见连续的红色就需要调整了。也就说当插入节点后,双亲节点和孩子节点都是红色就需要调整了。对比AVL树红黑树的调整情况可能多了一点,但是调整比较简单。大致分为改变颜色处理,和旋转处理这两种处理方式。同时有分为3个大情况,其实细分下来是6种情况的。我们来看看图吧。

在这里插入图片描述

上图包含了红黑树的所有处理情况,需要考虑grandfather,parent,uncle,cur,这4这个节点的关系。其实红黑树的处理是对称的,这里如果用文字叙述还是太繁琐了还是建议看图。我这里简单总结一下红黑树的处理方式吧。

如果存在叔叔节点,且叔叔节点是红色就需要用到变色处理。且变色处理方式统一为parent和uncle节点为黑色,grandfather为红色。

如果叔叔节点不存在或者叔叔节点黑色就需要旋转加变色处理。这里分为两大类,每类中又分为两小类。大类是根据grandfather和parent的关系划分的,小类是根据cur和parent的关系划分的。

第一大类:grandfather的左孩子是parent。情况1,cur是parent的左孩子。右旋grandfather,parent变黑,grandfather变红色。情况2,cur是parent的右孩子,先左旋parent再右旋grandfather,cur变黑色,grandfather变红色。

第二大类:grandfather的右边孩子是parent。情况1,cur是parent的左孩子。先右旋parent再左旋grandfather,cur变黑色,grandfather变红色。情况2,cur是parent的右孩子。左旋grandfather,parent变黑,grandfather变红色。

2.代码实现

之前介绍处理方式,接下来就是写代码了

代码示例

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

		Node* parent = nullptr;
		Node* 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);
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent)
			{  // 情况1:u存在且为红,变色处理,并继续往上处理
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					
					if (cur == parent->_left)
					{
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
			else // (grandfather->_right == parent)
			{
				
				Node* uncle = grandfather->_left;
				// 情况1:u存在且为红,变色处理,并继续往上处理
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else 
				{
					
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;

		return true;
	}

节点的定义

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)
	{}
};

这里我们写代码的时候,new出的节点默认应该是红色的。为什么呢?因为如果整棵树节点全是黑色的也是满足红黑树的性质,这样一来谈何变化呢?没变化有又怎么去做节点的调整处理呢?同样这也违背红黑树设计出来的初衷:构造一颗近似平衡的二叉搜索树。只有默认节点是红色这样一来才会有调整的可能。

这里每次插入后如果双亲节点和孩子节点都是红色就需要调整了。这里每次调整之后,最后一定要把root根节点设置成黑色。因为调整之后root节点可能不为黑了,这样才能保证红黑树的特性。关于代码细节实现还是需要对比上述图,且需要多画图自己体会一下。这些操作其实都是固定死的,但是想自己完整独立写出来还是需要多练习和画图思考的。

6.红黑树的检查

这个红黑树也是需要检查的,这里检查要针对红黑树的特性进行检查。

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

		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				++benchmark;
			}
			cur = cur->_left;
		}
		return _Check(_root, 0, benchmark);
	}
  bool _Check(Node* root, int blackNum, int benchmark)
	{
		if (root == nullptr)
		{
			if (benchmark != blackNum)
			{
				cout << "某条路径黑色节点的数量不相等" << endl;
				return false;
			}

			return true;
		}

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

		if (root->_col == RED
			&& root->_parent
			&& root->_parent->_col == RED)
		{
			cout << "存在连续的红色节点" << endl;
			return false;
		}

		return _Check(root->_left, blackNum, benchmark)
			&& _Check(root->_right, blackNum, benchmark);
	}

先检查红黑树的根节点是否为黑色,之后在求一条路径上的黑色节点数量,之后用于对比路径上的黑色节点个数。这里我选择的是一路向左,求得路径后,在写一个检查函数,用来对比所以个节点的路径上黑色节点的个数。如果出现了连续的红色节点也是错误的。这样检查红黑树就考虑的比较全面了。

完整代码

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)
	{}
};

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	~RBTree()
	{
		_Destroy(_root);
		_root = nullptr;
	}
public:
	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}

		return nullptr;
	}

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

		Node* parent = nullptr;
		Node* 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);
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					
					if (cur == parent->_left)
					{
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
			else // (grandfather->_right == parent)
			{
				
				Node* uncle = grandfather->_left;
				// 情况1:u存在且为红,变色处理,并继续往上处理
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else 
				{
					
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;

		return true;
	}

	void InOrder()
	{
		_InOrder(_root);
	}

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

		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				++benchmark;
			}
			cur = cur->_left;
		}


		return _Check(_root, 0, benchmark);
	}

	int Height()
	{
		return _Height(_root);
	}

private:
	void _Destroy(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}

	int _Height(Node* root)
	{
		if (root == NULL)
			return 0;

		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);

		return leftH > rightH ? leftH + 1 : rightH + 1;
	}

	bool _Check(Node* root, int blackNum, int benchmark)
	{
		if (root == nullptr)
		{
			if (benchmark != blackNum)
			{
				cout << "某条路径黑色节点的数量不相等" << endl;
				return false;
			}

			return true;
		}

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

		if (root->_col == RED
			&& root->_parent
			&& root->_parent->_col == RED)
		{
			cout << "存在连续的红色节点" << endl;
			return false;
		}

		return _Check(root->_left, blackNum, benchmark)
			&& _Check(root->_right, blackNum, benchmark);
	}

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

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

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
		{
			subRL->_parent = parent;
		}

		Node* ppnode = parent->_parent;

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

		if (ppnode == nullptr)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subR;
			}
			else
			{
				ppnode->_right = subR;
			}

			subR->_parent = ppnode;
		}
	}

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
		{
			subLR->_parent = parent;
		}
		Node* ppnode = parent->_parent;

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

		if (parent == _root)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subL;
			}
			else
			{
				ppnode->_right = subL;
			}
			subL->_parent = ppnode;
		}
	}

private:
	Node* _root = nullptr;
};

测试代码

#include"RBTree.h"
void Test_RBTree1()
{
	
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14, 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	RBTree<int, int> t1;
	for (auto e : a)
	{
		t1.Insert(make_pair(e, e));
	}

	t1.InOrder();
	cout << endl;
	cout << t1.IsBalance() << endl;
}

void Test_RBTree2()
{
	srand(time(0));
	const size_t N = 9999999;
	RBTree<int, int> t;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand() + i;
		t.Insert(make_pair(x, x));
	}

	cout << t.IsBalance() << endl;
	cout << t.Height() << endl;
}
int main()
{
	Test_RBTree1();
	Test_RBTree2();
	return 0;
}

7.总结

以上就红黑树和AVL树插入节点的操作了,红黑树和AVL树怎么说呢,它的相关操作都是固定死的,但是想正确流畅的写出来还是需要自己多练习和画图思考的,尤其是的画图,才能更为深刻理解旋转过程。以上内容如有问题,欢迎指正!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/514078.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

中国人民大学与加拿大女王大学金融硕士任晋红:热血中年

任晋红 中国人民大学-加拿大女王大学金融硕士 2021-2022级行业高管班 丰汇租赁有限公司 租赁事业部总经理 2021年9月初的一个傍晚&#xff0c;日暮四合&#xff0c;风轻轻亲了一下叶子&#xff0c;晚霞看见了&#xff0c;悄悄红了脸。线下上课时&#xff0c;窗外的晚霞是最美…

如何使用ADAudit Plus增强你的网络安全措施

网络安全是当今社会中至关重要的话题&#xff0c;因为互联网在我们生活中扮演着越来越重要的角色&#xff0c;人们对网络的依赖程度也越来越高。在这种情况下&#xff0c;网络内部安全威胁也变得越来越普遍。本文将介绍ADAudit Plus&#xff0c;它是一种用于检测和防范网络内部…

【RS专题】eval层混淆和逻辑完整分析 - 扣代码终结篇

如有侵权、联系本人下架 首先明确一下目标,我们要先获取网页200的源代码,RS5代第一次响应为412,第二次为200。如果是200就表示正常 以下为某 yjj RS5请求成功的结果,具体流程请看完文章,源-码–答-案也会在末 尾公 布 前面是定义了非常多和函数,一直往下拉,直到出现v…

游戏开发需要具备哪些技术

游戏开发需要具备哪些技术 大家好我是艾西&#xff0c;今天跟大家闲聊一下。开发一款游戏在没有参照物或是底板的情况下开发一款游戏都需要具备哪些技术&#xff0c;在开发过程中我们又会涉及到哪些板块或是领域。 一款游戏的开发需要一个总的游戏策划人&#xff0c;为游戏编写…

起重机安装拆除安全技术规范

为贯彻安全第一、预防为主、综合治理的方针&#xff0c;确保塔式起重机在安装、使用、拆卸时的安全&#xff0c;制定本规程。 本规程适用于房屋建筑工程、市政工程所用塔式起重机的安装、使用和拆卸。 本规程规定了塔式起重机的安装、使用和拆卸的基本技术要求。当本规程与国…

一些注意事项

1&#xff1a;Collection 子接口 1&#xff1a;List 鉴于 Java 中数组用来存储数据的局限性&#xff0c;我们通常使用 java.util.List 替代数组 List 集合类中元素有序、且可重复&#xff0c;集合中的每个元素都有其对应的顺序索引 1.1 List 接口主要实现类&#xff1a;Arra…

历经3个月,二战华为成功上岸,要个27k应该不过分吧~

先说下我基本情况&#xff0c;本科不是计算机专业&#xff0c;现在是学通信&#xff0c;然后做图像处理&#xff0c;可能面试官看我不是科班出身没有问太多计算机相关的问题&#xff0c;因为第一次找工作&#xff0c;华为的游戏专场又是最早开始的&#xff0c;就投递了&#xf…

嵌入式五大通信协议详解 (一) UART

嵌入式C语言学习进阶系列文章 GUN C编译器拓展语法学习笔记(一&#xff09;GNU C特殊语法部分详解 GUN C编译器拓展语法学习笔记(二&#xff09;属性声明 GUN C编译器拓展语法学习笔记(三&#xff09;内联函数、内建函数与可变参数宏 数组存储与指针学习笔记(一&#xff09;数…

Spring MVC 和 WebFlux 上传文件

WebFlux 上传文件 1. 表单上传方式1.1 Spring MVC1.2 Spring WebFlux 2. 二进制流2.1 Spring MVC2.2 Spring WebFlux 开发环境&#xff1a;jdk 11 WebFlux&#xff1a;jdk 8 1. 表单上传方式 1.1 Spring MVC multipart大小限制 spring:servlet:multipart:max-file-size: 512…

IS220PPRFH1B输电线路的先导继电器保护

​ IS220PPRFH1B输电线路的先导继电器保护 导引线差动继电器是专为保护配电线路和输电线路而设计的高速继电器&#xff0c;主要用于40公里以内的短线路。它是开关设备继电器中速度最快的功率继电器&#xff0c;该方案的工作需要通信通道&#xff0c;以便它可以将系统电压和电流…

2023.05.11-使用纯CPU来运行RWKV大语言模型

1. 简介 使用CPU来运行C版本的RWKV rwkv.cpp 可以将 RWKV 原始模型的参数转化为 float16&#xff0c;并量化到 int4&#xff0c;可以在 CPU 上更快地运行&#xff0c;也可以节省更多的内存。 2. 下载项目 ## git clone --recursive https://github.com/saharNooby/rwkv.cpp…

PostgreSQL11 | 视图

上一篇讲了索引&#xff0c;索引提高了表查询的速度&#xff0c;这一篇讲视图。 视图 视图&#xff0c;数据库中的一个虚拟表。 目录 视图 视图概述 前期准备 创建视图 单表视图 多表视图 查询视图 删除视图 视图概述 视图同真实表一样具有表的功能&#xff0c;但是…

spring事务失效的12种场景

前言 对于从事java开发工作的同学来说&#xff0c;spring的事务肯定再熟悉不过了。 在某些业务场景下&#xff0c;如果一个请求中&#xff0c;需要同时写入多张表的数据。为了保证操作的原子性&#xff08;要么同时成功&#xff0c;要么同时失败&#xff09;&#xff0c;避免…

H. Binary Craziness

题目链接 2023 Hubei Provincial Collegiate Programming Contest Examples input 6 6 1 3 2 3 1 4 2 5 3 6 4 6 output 30 input 6 4 1 2 3 5 2 4 3 6 output 0 题目大意&#xff1a; 给出结点个数 n n n和边的个数 m m m 下面依此给出 m m m个边&#xff0c;边是无向的&am…

Linux三种网络模式 | 仅主机、桥接、NAT

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; Linux三种网络模式 仅主机模式&#xff1a;虚拟机只能访问物理机&#xff0c;不能上网 桥接模式&#xff1a;虚拟机和物理机连接同一网络&#xff0c;虚拟机和物理机…

字典核心底层原理

字典对象的核心是散列表。散列表是一个稀疏数组&#xff08;总是有空白元素的数组&#xff09;&#xff0c;数组的每个单元叫做bucket。每个bucket有两部分&#xff1a;一个是键对象的引用&#xff0c;一个是值对象的引用。 由于&#xff0c;所有bucket结构和大小一致&#xf…

服装厂的管理系统如何选?内行人:这4点一定要注意!

服装厂管理水平偏低&#xff0c;耗费大量时间和资金成本&#xff0c;导致利润越来越低&#xff0c;是现在很多中小服装厂普遍面临的痛点。 依靠传统的管理模式&#xff0c;口头询问生产进度&#xff0c;手写统计数量&#xff0c;很显然不适合现代工厂的管理模式&#xff0c;服装…

java开发记录V1

编辑器vscode 在vscode中安装配置springboot 下载安装jdk oracle jdk BellSoft Liberica JDK version 17 在vscode编辑器中安装相关组件Extension Pack for Java、Spring Boot Extension Pack、Spring Initializr Java Support 创建springboot项目&#xff1a;ctrlshiftp后…

【Midjourney】Midjourney 辅助工具 ① ( 自定义命令工具 | 设置描述词 | 设置风格 | 设置灯光 | 设置相机 | 设置艺术家 )

文章目录 一、Midjourney Prompt Tool 自定义命令工具1、设置描述词2、设置风格3、设置灯光4、设置相机参数5、设置艺术家参数 Midjourney 提示词命令 可以使用 辅助工具 进行生成 , 辅助工具如下 : Midjourney Prompt Tool 自定义命令工具Midjourney Prompt Generator 命令生…

软件测试踏入这三个误区,就离滚蛋不远了

误区一&#xff1a;测试都是女生&#xff0c;男生不适合 误区二&#xff1a;这个职位很简单&#xff0c;不需要很多技术含量&#xff0c;每天很闲 误区三&#xff1a;起步即巅峰&#xff0c;薪资提升空间不大&#xff0c;一线才八九千 如果你听到这样的言论&#xff0c;赶紧走…