二叉搜索树、AVL树、红黑树底层源码以及迭代器模拟实现,map/set的封装

news2024/11/24 13:21:30

这次给大家分享的还是关于二叉树部分的内容,之前的文章已经分享过一些二叉树的基础知识,如果不了解的朋友可以看看:二叉树以及堆和堆排序。普通的二叉树其实是没有什么实际的应用价值的,而map和set大家用过或者听过吗?这是不管C++还是java等高级编程语言都有的数据结构。所以重要性不言而喻,而map/set的底层存储结构就是一颗二叉树,但是可不是简单的二叉树那么简单,至于其中奥妙需要我们共同去学习。

话不多说我们继续往下看。

  • 二叉搜索树
    • 二叉搜索树的概念:
    • 搜索二叉树的插入
    • 二叉搜索树的查找
    • 二叉搜索树的删除
  • AVL树
    • AVL树的概念
    • AVL树的插入
    • 判断AVL树的平衡
  • 红黑树
    • 红黑树的概念
    • 红黑树的插入
    • 检查是否为红黑树
  • set/map的封装和迭代器的实现

二叉搜索树

二叉搜索树的概念:

	二叉搜索树也成为二叉排序树,或者是一颗空树,如果不为空树拥有以下特性。
  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值。
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值。
  • 它的左右子树也分别为二叉搜索树。

在这里插入图片描述
这棵树就满足二叉搜索树的特性,左子树所有的节点都小于根节点,右子树所有的节点都大于根节点,如果中序遍历这颗搜索树,你就会发现,数据是升序序列。

这就是一颗树结点的定义。

	template <class K>
	struct BSTreeNode {
		BSTreeNode<K>* _left;
		BSTreeNode<K>* _right;
		K _key;
	public:
		BSTreeNode(const K& key)
			:_key(key)
			, _left(nullptr)
			, _right(nullptr)
		{}
	};

搜索二叉树的插入

插入其实非常简单,往一颗二叉树里面插入一个值,如果比根节点大,就往右子树走,比根节点小,就往左子树遍历。

bool Insert(const K& key) {
			if (_root == nullptr) { //如果根节点为空,树就为空,这个新插入的值就是根节点
				_root = new Node(key);
				return true;
			}
			else {
				Node* parent = nullptr;//需要记录父节点,让新插入的节点链接到父结点上。
				Node* cur = _root;
				while (cur) {
					if (cur->_key < key) {	//如果比根节点大,需要走右子树
						//需要更新父节点,让父节点跟着cur往下找。
						parent = cur;
						cur = cur->_right;
					}
					else if (cur->_key > key) { //如果比根节点小,需要走左子树
						parent = cur;
						cur = cur->_left;
					}
					else { //如果不大不小证明,这个值的节点已经存在了,不需要插入了,返回false;
						return false;
					}
				}
				//从循环出来证明,cur为空,已经走到了合适的位置。直接申请一个节点空间
				cur = new Node(key);
				//还需要让父节点指向它,如果比父节点大,让右指针指向它,否者左指针指向它。
				if (parent->_key < key) {
					parent->_right = cur;
				}
				else {
					parent->_left = cur;
				}
				return true;
			}
		}

二叉搜索树的查找

查找也是非常简单,我就不做过多的说明了,如果有细心的朋友会发现在插入里面其实就包含查找的代码和说明

	bool Find(const K& key) {
		Node* cur = _root;
		while (cur) {
			if (cur->_key < key) {
				cur = cur->_right;
			}
			else if (cur->_key > key) {
				cur = cur->_left;
			}
			else {
				return true;
			}
		}
		return false;
	}

二叉搜索树的删除

对比插入和查找,其实删除才是有点难度的,如果面试的话,也只会考察删除,所以是学习的重点。

二叉搜索树删除一个节点有三种情况是需要被处理的:
在这里插入图片描述

三种情况的处理方法:

  • 情况一:把右子树的节点给了父节点,至于给父的左还是右,需要判断cur是父的左还是右。
  • 情况二:把左子树的节点给了父节点。
  • 情况三:找左子树的最大节点或者右子树的最小节点替代当前节点,上图的cur,可以当它是根节点,也可以当它是树的子树。

在这里插入图片描述
这就是删除的三种情况,已经详细的给大家介绍了解决方法,我们可以看看代码是如何控制实现的。

bool Erase(const K& key) {
			Node* parent = nullptr;
			Node* cur = _root;
			while (cur) {
				if (cur->_key < key) { 
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key) {
					parent = cur;
					cur = cur->_left;
				}
				else { //找到要删除的节点
					//情况一:左为空
					if (cur->_left == nullptr) {
						//需要判断是不是根节点,根节点的parent为空
						if (cur == _root) {
							_root = cur->_right;
						}
						else {
							//如果是父节点的左就把右子树给了父节点的左,否则给父节点的右。
							if (parent->_left == cur) {
								parent->_left = cur->_right;
							}
							else {
								parent->_right = cur->_right;
							}
						}
						delete cur;
					}//情况二:右为空
					else if (cur->_right == nullptr)
					{
						if (cur == _root) {
							_root = cur->_left;
						}
						else {
							//如果是父节点的左就把左子树给了父节点的左,否则给父节点的右。
							if (parent->_left == cur) {
								parent->_left = cur->_left;
							}
							else {
								parent->_right = cur->_left;
							}
						}
						delete cur;
					}
					else { //情况三: 左子树和右子树都不为空
					
						Node* pminRight = cur;
						Node* minRight = cur->_right;//右子树最小节点存储
						
						//寻找右子树最小节点
						while (minRight->_left) {
							pminRight = minRight;//记录右子树最小节点的父节点
							minRight = minRight->_left;
						}
						
						//把右子树最小节点的值给了cur
						cur->_key = minRight->_key;
						
						//右子树最小节点代表左子树一定为空,右子树可能存在,需要处理。如果右子树最小节点是父节点的左就把右子树最小节点的右子树给了父节点的左,否则给父节点的右。
						if (pminRight->_left == minRight) {

							pminRight->_left = minRight->_right;
						}
						else {
							pminRight->_right = minRight->_right;
						}
						delete minRight;
					}
					return true;
				}
			}
			return false;
		}

这就是普通的搜索二叉树,如果核心逻辑和代码实现已经讲清楚了,如果想要所有的源代码,可以在我的gitee上下载:二叉搜索树源码

AVL树

上面介绍了普通的二叉搜索树,看起来它的查找效率好像是log2n,但是真的是这样吗?我们可以看看下面这张图。他已经变成了一颗歪脖子树,插入、删除、查找都和一个链表一样,这就失去了它的价值。针对于这种情况,AVL树可以很好的解决它。
在这里插入图片描述

AVL树的概念

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

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  • 它的左右子树都是AVL树。
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)。

就是树的每个节点都有一个平衡因子,0代表左子树和右子树的高度相同,1代表右子树比左子树高一个,-1代表右子树比左子树低一个(也可以是-1代表右子树比左子树高一个,1代表右子树比左子树低一个)。
在这里插入图片描述
AVL树节点代码定义

template<class K,class V>
class 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)
		,_kv(kv)
		,_bf(0)
	{}
};

AVL树是如何保证它的平衡性的呢?如果插入一个节点导致平衡性被破坏了要如何调整呢?我们继续往下看。

AVL树的插入

插入节点:

  1. 先按照二叉搜索树的规则将节点插入到AVL树中
  2. 新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了AVL树。

调整平衡因子

Cur插入后,Parent的平衡因子一定需要调整,在插入之前,pParent的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:
  1. 如果pParent的平衡因子为0,说明插入之前pParent的平衡因子为正负1,插入后被调整成0,此时满足AVL树的性质,插入成功。
  2. 如果pParent的平衡因子为正负1,说明插入前pParent的平衡因子一定为0,插入后被更新成正负1,此时以pParent为根的树的高度增加,需要继续向上更新。
  3. 如果pParent的平衡因子为正负2,则pParent的平衡因子违反平衡树的性质,需要对其进行旋转处理。

旋转的四种情况:
左单旋
在这里插入图片描述
左单旋代码实现

void RotateL(Node* parent)
	{
		Node* RChild = parent->_right;
		Node* RChild_L = RChild->_left;

		parent->_right = RChild_L;
		if (RChild_L)
			RChild_L->_parent = parent;

		Node* grandparent = parent->_parent;
		RChild->_left = parent;
		parent->_parent = RChild;

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

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

	}

右单旋
在这里插入图片描述

void RotateR(Node* parent)
	{
		Node* LChild = parent->_left;
		Node* LChild_R = LChild->_right;
		parent->_left = LChild_R;

		if (LChild_R)
			LChild_R->_parent = parent;

		Node* grandparent = parent->_parent;

		LChild->_right = parent;
		parent->_parent = LChild;
		if (grandparent == nullptr)
		{
			_root = LChild;
			_root->_parent = nullptr;
		}
		else 
		{
			if (grandparent->_left == parent)
			{
				grandparent->_left = LChild;
			}
			else
			{
				grandparent->_right = LChild;
			}
			LChild->_bf = parent->_bf = 0;
		}
	}

左右双旋:先左单旋再有单旋
在这里插入图片描述

void RotateLR(Node* parent)
	{
		Node* LChild = parent->_left;
		Node* LChild_R = LChild->_right;
		int bf = LChild_R->_bf;

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

		if (bf == 1)
		{
			parent->_bf = 0;
			LChild_R->_bf = 0;
			LChild->_bf = -1;
		}
		else if(bf == -1)
		{
			parent->_bf = 1;
			LChild_R->_bf = 0;
			LChild->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			LChild_R->_bf = 0;
			LChild->_bf = 0;

		}
		else {
			assert(false);
		}
	}

右左双旋:先右单旋,再左单旋
在这里插入图片描述

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

		RotateR(RChild);
		RotateL(parent);

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

插入代码

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.frist < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.frist > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else {
				return false;
			}
		}

		cur = new Node(kv);
		while (parent) 
		{	
			//更新父节点平衡因子
			if (cur == parent->_right)
			{
				parent->_bf++;
			}
			else {
				parent->_bf--;
			}
			
			//更新完如果为1或者-1代表这颗树的高度增加了,需要继续向上更新祖宗节点的平衡因子
			if (parent->_bf == 1 || parent->_bf == -1)
			{
				//
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (parent->_bf == 0)	//更新完为0,代表高度没有变化,直接退出
			{
				break;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)//更新完之后变为-2或者2,需要旋转调整
			{
				//具体怎么旋转根据旋转之前的平衡因子判断
				if (parent->_bf == 2 && cur->_bf == 1) {
					RotateL(parent);//进行左单旋
				}
				else if (parent->_bf == -2 || cur->_bf == -1)
				{
					RotateR(parent); //进行右单旋
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(parent);//左右双旋
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);//右左双旋
				}
				else {
					assert(false);
				}
				break;
			}
			else
			{
				assert(false);
			}
		}
		return true;
	}

判断AVL树的平衡

AVL树对平衡性的要求是非常严格的,写完我们并不知道是否是真正保证了平衡性,所以需要代码验证。

bool IsBalance() {
	return _IsBlance(_root);
}
int Height() {
	_Height(_root);
}
//求一颗树的高度
int _Height(Node* root) {
		if (root == nullptr) {
			return 0;
		}
		int left = _Height(root->_left);
		int right = _Height(root->_right);
		if (right - left != root->_bf) {
			assert(false);
		}
		return left > right ? left + 1 : right + 1;
}
bool _IsBlance(Node* root) {
	if (root == nullptr) {
		return true;
	}
	int leftH = _Height(root->_left);//求左子树的高度
	int rightH = _Height(root->_right);//求右子树的高度

	return abs(leftH - rightH) < 2 && //保证这个节点的左右子树的绝对值小于2
		_IsBlance(root->_left) && 	//需要递归求证左右子树的子树高度差不大于2
		_IsBlance(root->_right);
}

这就是AVL树的概念和基本操作,如果想要完整的源代码,可以在我的gitee上获取:AVL树

红黑树

学习了上面的AVL树,好像不管是插入数据还是查询,效率都变得极高,但是有一个缺点就是,对平衡新的要求很高,只要高度差一就需要立即调整,这就意味着他可能需要大量的调整,所以我们可以学习一下红黑树。红黑树是一个重头戏,红黑树可能有的人听过或者没听过,但是学习高级编程语言,红黑树几乎是必学,因为map和set的底层就是一颗红黑树。
在这里插入图片描述

红黑树的概念

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

红黑树到底是如何通过两种颜色就控制路径的长度的呢?还需要了解一下红黑树的性质。
红黑树的性质:

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

通过上面几条性质,我们总结一下为什么红黑树最短路径不会超过最长路径的2倍。

根据性质3,我们可以看出来,一个红色节点的孩子节点必须是黑色节点,代表,一条路径不能有两个连续的红色节点。
根据性质4,每条路径的黑色节点必须都是一样的,也就代表如果这棵树的最短路径是全黑色节点,最长路径最长只能是一黑一红,因为不能连续的红色节点,每条路径的黑色节点必须是一样的。所以最长路径不会超过最短路径的二倍。

红黑树节点定义

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) //新增节点默认为红色,方便处理
	{}
};

红黑树的插入

想要从一个空树,通过插入变成一颗红黑树,究竟怎么实现的,需要进行哪些转换我们继续往下看:

红黑树的插入分为几种情况:

  1. 该树为空树,直接插入根结点的位置,违反性质2,把节点颜色有红改为黑即可。

  2. 插入节点cur的父节点P为黑色,不违反任何性质,无需做任何修改。
    在这里插入图片描述

  3. cur为红,Parent为红,(祖节点一定存在,且为黑)Uncle存在并且也为红,需要进行变色处理。
    在这里插入图片描述

  4. cur为红,parent为红,uncle为黑或者不存在,这两种情况都是一样的处理方式,需要先旋转再变色。旋转完不用继续往上更新。需要旋转的情况比较多,给大家列举几种代表性的,剩下的旋转方式和AVL树使一样的。旋转的代码直接服用AVL树的就可以。我就不重复贴了。
    在这里插入图片描述

在这里插入图片描述

最后根节点一定要变黑,至于更详细的变色情况,代码里面已经表达清楚了。

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;
			//uncle存在且为红,变色处理,并继续往上处理
			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) //父节点是祖父节点的左孩子,cur是parent的左孩子只需要进行右单旋
					{
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else	//父节点是祖父节点的左孩子,cur是parent的右孩子只需要进行左右双旋
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
			else //父节点是祖父节点的右孩子
			{
				Node* uncle = grandfather->_left;
				//如果叔叔节点存在且为红,往上更新
				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;
	}

检查是否为红黑树

检查一颗树是否为红黑树,检查方式还是

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

		int benchmark = 0;
		Node* cur = _root;
		//先查看其中一条路径的黑色节点,记录在benchmark里面
		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)
		{	
			//检查这条路径的黑色节点和benchmark是否相同,如果不相同说明不是红黑树
			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);
	}

这就是如何从0到生成一颗红黑树以及它是如何维护的具体过程。想要具体的源码可以登录gitee查看:红黑树底层实现

set/map的封装和迭代器的实现

set和map底层都是一颗红黑树,但是set存储的是key,map存储的是key/value值,是不是需要为set和map各写一份红黑树吗?答案当然是不需要,我们可以借助模板参数进行封装

这是红黑树节点的定义,它只有一个模板参数,不管是存pair还是key都可以。

template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data;
	Colour _col;
	RBTreeNode(const T& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(RED)
	{}
};
这个红黑树源码太多了,所以我截去了一部分进行讲解,如果想要完整的源码,我一会把gitee链接贴到文章末尾。
我还是拿代码进行讲解,详情看代码注释讲解。
//模板参数改成了三个,其中第一个就是key,第二个参数set还是传key,如果是map就传入pair对象,第三个参数是比较大小方式
template<class K, class T ,class KeyOfT>
class RBTree
{
public:
	typedef  RBTreeNode<T> Node;
	typedef _RBTreeIterator<T, T&, T*> iterator;
	typedef _RBTreeIterator<T, const T&, const T*> const_iterator;
	iterator begin()
	{
		Node* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return iterator(cur);
	}
	iterator end()
	{
		return iterator(nullptr);
	}
	//返回pair对象,第二个bool值代表插入成功,成功true,失败false,第一个参数返回成功插入节点的迭代器,失败的话代表值存在,返回存在值的迭代器
	pair<iterator, bool> Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(iterator(_root),true);
		}
		KeyOfT kot;
		Node* parent = nullptr;
		Node* cur = _root;

		while (cur)
		{
			if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kot(cur->_data) > kot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return make_pair(iterator(cur), false);
			}
		}
		cur = new Node(data);
		Node* newnode = cur;
		if (kot(parent->_data) > kot(data))
		{
			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;
					grandfather = 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
			{
				Node* uncle = grandfather->_left;
				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 make_pair(iterator(newnode), true);
	}


private:
	Node* _root = nullptr;
};

我们看看set是如何对RBTree进行封装的。

#pragma once
#include "tree.h"
namespace JRG {
	template<class K>
	class set
	{	
		//
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		//普通迭代器和const迭代器都是const迭代器,应为key不允许修改
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;

		iterator begin()
		{
			return _t.begin();
		}
		iterator end()
		{
			return _t.end();
		}
		pair<iterator,bool> insert(const K& key)
		{
			return _t.Insert(key);
		}
	private:
		RBTree<K, K, SetKeyOfT> _t; //第一个参数和第二个参数都传入key,第三个参数传入类
	};
}

map的封装和set大同小异

#pragma once
namespace JRG {
	template<class K,class V>
	class map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<const K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
		iterator begin()
		{
			return _t.begin();
		}
		iterator end()
		{
			return _t.end();
		}
		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = _t.Insert(key);
			return ret.first->second;
		}
		pair<iterator, bool> insert(const pair<const K, V>& kv)
		{
			return _t.Insert(kv);
		}
	private:
		RBTree<K, pair<const K, V>, MapKeyOfT> _t;
	};
}

重点是RBTree的迭代器的实现,同时兼容了set和map,设计的非常巧妙

template<class T,class Ref,class Ptr>
struct _RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef _RBTreeIterator<T, Ref, Ptr> Self;
	Node* _node;
	_RBTreeIterator(Node* node)
		:_node(node)
	{}
	//这个部分专门为set设计的,因为set里面的迭代器都是const迭代器,而set调用的begin返回值却是普通迭代器,所以需要可以用普通迭代器构造const迭代器
	_RBTreeIterator(const _RBTreeIterator<T, T&, T*>& it) 
		:_node(it._node)
	{}
	Ref operator*()
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &_node->_data;
	}
	bool operator!=(const Self& s)
	{
		return _node != s._node;
	}
	//默认是中序遍历红黑树,所以模拟中序遍历迭代
	Self& operator++()
	{	
		//如果右不为空迭代右子树
		if (_node->_right)
		{
			Node* subLeft = _node->_right;
			while (subLeft->_right)
			{
				subLeft = subLeft->_right;
			}
			_node = subLeft;
		}
		else	//代表右为空,那么这颗子树遍历完了,需要找孩子是父节点的左子树的节点。
		{
			Node* cur = _node;
			Node* parent = _node->_parent;
			while (parent&&cur != parent->_left) {
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}
	Self& operator--()
	{
		if (_node->_left)
		{
			Node* subRight = _node->_left;
			while (subRight->_right)
			{
				subRight = subRight->_right;
			}
			_node = subRight;
		}
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}
	Self operator++(int)
	{
		Self s = *this;
		if (_node->_right)
		{
			Node* subLeft = _node->_right;
			while (subLeft->_right)
			{
				subLeft = subLeft->_right;
			}
			_node = subLeft;
		}
		else
		{
			Node* cur = _node;
			Node* parent = _node->_parent;
			while (parent && cur != parent->_left) {
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return s;
	}
};

这就是map和set的封装以及迭代器的实现,如果需要需要完整源代码可以点击链接:红黑树全部源代码

这就是全部的内容,希望对您有所帮助。

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

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

相关文章

Metasploit Framework(MSF)对Metasploitable2的渗透解析

简介 Metasploitable2虚拟系统是一个特殊的ubuntu操作系统&#xff0c;本身设计目的是作为安全工具测试和演示常见漏洞攻击的环境。 其中最核心是可以用来作为MSF攻击用的靶机。这样方便我们学习MSF框架的使用。 并且开放了很多的高危端口如21、23、445等&#xff0c;而且具有…

李薇:大模型时代的数据变革

Datawhale干货 作者&#xff1a;李薇&#xff0c;上海人工智能实验室 前言 今天&#xff0c;我将向那些希望深入了解大模型的同学们&#xff0c;分享一些关于大模型时代的数据变革的知识。作为上海人工智能实验室OpenDataLab的产品主管&#xff0c;我会介绍我们在开放数据和大…

大数据技术闲侃之岗位选择解惑

前言 写下这篇文章是因为五一节前给群友的承诺&#xff0c;当然按照以往的惯例&#xff0c;也是我背后看到的这个现象&#xff0c;我发现大部分同学在投递岗位的时候都是投递数据分析岗位&#xff0c;其实背后并不是很清楚背后的岗位是做啥的&#xff0c;想想我自己的工作生涯…

用户/用户组管理

用户管理 * useradd 命令添加用户&#xff0c;会在/etc/passwd生成用户信息&#xff0c;信息分为7列&#xff0c;被6个冒号隔开 第一列 username (login name) 第二列 密码&#xff0c;但是该列已经被移除&#xff0c;用x表示&#xff0c;密码信息已经存放在了/etc/shadow文…

Android以aar包形式引入hunter-debug,Java(3)

Android以aar包形式引入hunter-debug&#xff0c;Java&#xff08;3&#xff09; &#xff08;1&#xff09;首先把hunter的master分支代码拉下来&#xff0c;在本地编译&#xff0c; https://github.com/Leaking/Hunterhttps://github.com/Leaking/Hunter此过程主要目的是获得…

理解学习曲线:芯片工作中的平台价值和个人价值

作为一名芯片工程师&#xff0c;从毕业出到步入公司的第一天开始&#xff0c;需要完成一次明显的转变&#xff0c;随着工作的日益开展和项目推进&#xff0c;个人能力的也得到了潜移默化的提升&#xff0c;当我们回看个人的知识/技能成长的曲线时&#xff0c;可能会发现很多的发…

CMake:递归检查并拷贝所有需要的DLL文件

文章目录 1. 目的2. 设计整体思路多层依赖的处理获取 DLL 所在目录探测剩余的 DLL 文件 3. 代码实现判断 stack 是否为空判断 stack 是否为空获取所有 target检测并拷贝 DLL 4. 使用 1. 目的 在基于 CMake 构建的 C/C 工程中&#xff0c;拷贝当前工程需要的每个DLL文件到 Visu…

将nacos从本地切换到远程服务器上时报错:客户端端未连接,Client not connected

报错信息&#xff1a; 09:34:38.438 [com.alibaba.nacos.client.Worker] ERROR com.alibaba.nacos.common.remote.client - Send request fail, request ConfigBatchListenRequest{headers{charsetUTF-8, Client-AppNameunknown, Client-RequestToken65c0fbf47282ae0a7b85178…

android点击事件,跳转界面

Android 事件处理 1&#xff0c;采用在Activity中创建一个内部类定义点击事件 主要xml代码 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:app"http:…

【SAP Abap】X-DOC:SE37 - ABAP 功能模块之更新模块(Function Module 之 Update module)

【SAP Abap】X-DOC&#xff1a;SE37 - ABAP 功能模块之更新模块&#xff08;Function Module 之 Update module&#xff09; 1、简介1.1、什么是更新函数1.2、更新函数的类型1.3、更新函数的参数要求1.4、更新函数的调用方式1.5、更新函数的调试方式1.6、更新任务的执行模式1.7…

C语言——控制语句

目录 1. 分支语句1.1 if语句1.1.1 基本结构1.1.2 分层结构1.1.3 嵌套结构 1.2 switch case 语句 2.循环语句2.1 for循环2.1.1 基本结构2.1.2 嵌套结构2.1.3 变形 2.2 while循环2.3 do while循环2.4 死循环2.5 循环控制语句 控制语句即用来实现对程序流程的选择、循环、转向和返…

Shiro框架漏洞分析与复现

Shiro简介 Apache Shiro是一款开源安全框架&#xff0c;提供身份验证、授权、密码学和会话管理。Shiro框架直观、易用&#xff0c;同时也能提供健壮的安全性&#xff0c;可以快速轻松地保护任何应用程序——从最小的移动应用程序到最大的 Web 和企业应用程序。 1、Shiro反序列…

Linux下的线程(线程的同步与互斥)

目录 Linux下线程创建函数pthread_ create() 线程的等待函数pthread_ join() 线程终止 函数pthread exit() 函数pthread_cancel() 分离线程pthread_detach() 线程间的互斥 线程间同步 死锁 进程和线程 线程和进程是一对有意义的概念&#xff0c;主要区别和联系如下&am…

【Linux】常见指令以及权限理解

&#x1f307;个人主页&#xff1a;平凡的小苏 &#x1f4da;学习格言&#xff1a;别人可以拷贝我的模式&#xff0c;但不能拷贝我不断往前的激情 &#x1f6f8;C专栏&#xff1a;Linux修炼内功基地 家人们更新不易&#xff0c;你们的&#x1f44d;点赞&#x1f44d;和⭐关注⭐…

Vue 中动态引入图片为什么要是 require

在vue中动态的引入图片为什么要使用require&#xff1f; 因为动态添加src被当做静态资源处理了&#xff0c;没有进行编译&#xff0c;所以要加上require&#xff0c; 我倒着都能背出来...... emmm... 乍一看好像说的很有道理啊&#xff0c;但是仔细一看&#xff0c;这句话说…

《设计模式》之单例模式

文章目录 1、定义2、动机2、类结构3、单例的表现形式4、总结4、代码实现(C) 1、定义 保证一个类仅有一个实例&#xff0c;并提供一个该实例的全局访问点。 2、动机 在软件系统中&#xff0c;经常有这样一些特殊的类&#xff0c;必须保证它们在系统中只存在一个实例&#xff…

可道云上传文件后报错: Call to undefined function shell_exec()

宝塔面板中直接一键部署的可道云&#xff0c;使用的是PHP8.0环境&#xff0c;上传文件或者点击我刚上传好的文件夹就会报错以下错误&#xff1a; 出错了! (warning!) Call to undefined function shell_exec() 系统错误 fileThumb/app.php[376] fileThumbPlugin->checkB…

UML时序图详解

上篇文章&#xff0c;介绍了UML状态图&#xff0c;并通过visio绘制一个全自动洗衣机的UML状态图实例进行讲解。 本篇&#xff0c;来继续介绍UML中的另一种图——时序图。 1 时序图简介 时序图(Sequence Diagram)&#xff0c;也叫顺序图&#xff0c;或序列图&#xff0c;是一…

基于SpringBoot的招聘信息管理系统设计与实现

前言 本次设计任务是要设计一个招聘信息管理系统&#xff0c;通过这个系统能够满足管理员&#xff0c;用户和企业的招聘信息管理功能。系统的主要功能包括首页、个人中心、用户管理、企业管理、工作类型管理、企业招聘管理、投简信息管理、面试邀请管理、求职信息管理、社区留…

银行数字化转型导师坚鹏:银行数字化转型的5大发展趋势

银行数字化转型的发展趋势主要包括以下5个方面&#xff1a; 从过去的局部数字化转型向全面数字化转型转变&#xff1a;2022年1月&#xff0c;中国银保监会发布《关于银行业保险业数字化转型的指导意见》&#xff0c;标志着中国银行业的数字化转型已经不是过去银行自己主导的局…