【高阶数据结构】封装Map和Set

news2025/1/21 15:41:10

🌈欢迎来到数据结构专栏~~封装Map和Set


  • (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是Scort
  • 目前状态:大三非科班啃C++中
  • 🌍博客主页:张小姐的猫~江湖背景
  • 快上车🚘,握好方向盘跟我有一起打天下嘞!
  • 送给自己的一句鸡汤🤔:
  • 🔥真正的大师永远怀着一颗学徒的心
  • 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏
  • 🎉🎉欢迎持续关注!
    在这里插入图片描述

请添加图片描述

文章目录

  • 🌈欢迎来到数据结构专栏~~封装Map和Set
    • 一. 红黑树源码
    • 二. 观察源码
      • 🥑底层RBTree的结构
      • 🥑底层的Key和Map
    • 二. 参数的适配
    • 三. 数据的存储
    • 四. 仿函数的支持
    • 五. 迭代器实现
      • 🎨正向迭代器
      • 🎨反向迭代器
    • Set的实现
    • Map的实现
    • 红黑树的代码
  • 📢写在最后

请添加图片描述

一. 红黑树源码

虽然 set 参数只有 key,但是介于 map 除了 key 还有 value;我们任然将对一棵KV模型的红黑树进行封装,

//枚举颜色
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>
struct RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	//如果是空树,则插入节点作为root节点
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;//根节点必须是黑色
			return true; //插入成功
		}

		//按二叉搜索树的插入方法,找到待插入位置
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (kv.first > cur->_kv.first)//待插入结点的key值大于当前结点的key值
			{
				//往节点的右子树走
				parent = cur;
				cur = cur->_right;
			}
			else if (kv.first < cur->_kv.first)//待插入结点的key值小于当前结点的key值
			{
				//往节点的左子树走
				parent = cur;
				cur = cur->_left;
			}
			else//插入的值等于当前的节点,返回失败
			{
				return false;
			}
		}

		//将节点链接到树上
		cur = new Node(kv);//构造节点 
		cur->_col = RED;

		if (kv.first < parent->_kv.first)		//判断链接左还是右?
		{
			//插入到左边
			parent->_left = cur;
			cur->_parent = parent;
		}
		else if (kv.first > parent->_kv.first)
		{
			//插入到右边
			parent->_right = cur;
			cur->_parent = parent;
		}

		//如果插入节点的父节点是红色的,则需要对红黑树进行操作
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			assert(grandfather);
			assert(grandfather->_col == BLACK);

			//关键看叔叔  ~ 判断叔叔的位置
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				//情况1:uncle存在且为红  + 继续往上处理
				if (uncle && uncle->_col == RED)
				{
					//变色:p和u变黑,g变红
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续往上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else  //情况2 + 情况3:uncle不存在 + uncle存在且为黑
				{
					//情况二:单旋 + 变色
					//    g
					//  p   u
					//c            
					if (cur = parent->_left)
					{
						RotateR(grandfather);//右旋

						//颜色调整
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else//cur == parent->_right
					{
						//情况三:左右双旋 + 变色
						//    g
						//  p   u
						//    c 
						RotateL(parent);
						RotateR(grandfather);

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

					//继续往上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else  //情况2 + 情况3:uncle不存在 + uncle存在且为黑
				{
					//情况二:单旋 + 变色
					//    g
					//  u   p
					//        c            
					if (cur = parent->_right)
					{
						RotateL(grandfather);//左单 旋

						//颜色调整
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else//cur == parent->_left
					{
						//情况三:右左双旋 + 变色
						//    g
						//  u   p
						//    c 
						RotateR(parent);
						RotateL(grandfather);

						//调整颜色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
		}
		_root->_col = BLACK;//不管什么,最后根要变黑
		return true;
	}

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

	bool IsBalance()
	{
		if (_root == nullptr)
		{
			return true;
		}

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

		// 黑色节点数量基准值
		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				++benchmark;

			cur = cur->_left;//以最左的路径进行
		}

		return PrevCheck(_root, 0, benchmark);
	}


private:
	bool PrevCheck(Node* root, int blackNum, int& benchmark)
	{
		if (root == nullptr)
		{
			//cout << blackNum << endl;
			//return;
			if (benchmark == 0)
			{
				benchmark = blackNum;
				return true;
			}

			if (blackNum != benchmark)
			{
				cout << "某条黑色节点的数量不相等" << endl;
				return false;
			}
			else
			{
				return true;
			}
		}

		if (root->_col == BLACK)
		{
			++blackNum;
		}
		//检测它的父亲
		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << "存在连续的红色节点" << endl;
			return false;
		}

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

	void _InOrder(Node* root)
	{
		if (root == nullptr)//空树也是红黑树
			return;
		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}
	//左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* parentParent = parent->_parent;

		//建立subRL与parent之间的联系
		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		//建立parent与subR之间的联系
		subR->_left = parent;
		parent->_parent = subR;

		//建立subR与parentParent之间的联系
		if (parentParent == nullptr)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (parent == parentParent->_left)
			{
				parentParent->_left = subR;
			}
			else
			{
				parentParent->_right = subR;
			}
			subR->_parent = parentParent;
		}
	}

	//右单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* parentParent = parent->_parent;

		//建立subLR与parent之间的联系
		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		//建立parent与subL之间的联系
		subL->_right = parent;
		parent->_parent = subL;

		//建立subL与parentParent之间的联系
		if (parentParent == nullptr)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (parent == parentParent->_left)
			{
				parentParent->_left = subL;
			}
			else
			{
				parentParent->_right = subL;
			}
			subL->_parent = parentParent;
		}
	}

	//左右双旋
	void RotateLR(Node* parent)
	{
		RotateL(parent->_left);
		RotateR(parent);
	}

	//右左双旋
	void RotateRL(Node* parent)
	{
		RotateR(parent->_right);
		RotateL(parent);
	}

private:
	Node* _root = nullptr;
};

二. 观察源码

🥑底层RBTree的结构

RBTree是根据传的Value的值来判断是什么类型,也就是一棵泛型的RBTree,通过不同的实例化,实现出了Map和Set传Key就是Set,传pair就是Map

在这里插入图片描述

🥑底层的Key和Map

可见set传的是Key,Map传的是pair
在这里插入图片描述

二. 参数的适配

为了实现泛型RBTree,对此我们将红黑树第二个模板参数的名字改为T,让自动识别是map还是set

template<class K, class T>
struct RBTree

T模板参数可能只是键值Key,也可能是由Key和Value共同构成的键值对。如果是set容器,那么它传入底层红黑树的模板参数就是Key和Key:

template<class K>
class set
{
public//...
private:
	RBTree<K, K> _t;
};

但如果是map容器,那么它传入底层红黑树的模板参数就是Key以及Key和Value构成的键值对:

template<class K, class V>
class map
{
public:
	//...
private:
	RBTree<K, pair<K, V>> _t;
};

那么问题来了:如果只看T的判断的话,是不是可以只保留第二个模板参数呢?

1️⃣对于Insert来说确实是这样的,因为此时红黑树的第二个模板参数当中也是有键值Key的,但是Key实际上是不可以省略的!

2️⃣对于set容器来说,省略红黑树的第一个参数当然没问题,因为set传入红黑树的第二个参数与第一个参数是一样的。但是对于map容器来说就不行了,因为map容器所提供的接口当中有些是只要求给出键值Key的,比如find和erase

三. 数据的存储

红黑树的模板参数变成了K和T,那节点存的是什么呢?
看了源码得知

  • set容器:K和T都代表键值Key
  • map容器:K代表键值Key,T代表由Key和Value构成的键值对

对于set容器来说,底层红黑树结点当中存储K和T都是一样的,但是对于map容器来说,底层红黑树就只能存储T了。由于底层红黑树并不知道上层容器到底是map还是set,因此红黑树的结点当中直接存储T就行了

这样一来就可以实现泛型树了,当上层容器是set的时候,结点当中存储的是键值Key;当上层容器是map的时候,结点当中存储的就是<Key, Value>键值对

在这里插入图片描述

template<class T>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;//三叉链
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;

	T _data;//存储的数据
	Colour _col;//节点颜色

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

四. 仿函数的支持

那我们插入比较的时候用data去比较吗?

  • 对于set而言是Key,可以比较
  • 但是对于map是pair,那我们要取其中的first来比较,但是我们能取first吗?
  • 这个地方的data有可能是map;也有可能是set

那就只能我们自己实现一个仿函数了,如果是map那就是用于获取T当中的键值Key,当红黑树比较的时候就是仿函数去获取

仿函数,就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了

template<class K, class V>
class map
{
	struct MapKeyOfT
	{
		const K& operator()(const pair<K, V>& kv)
		{
			return kv.first;
		}
	}
private:
	RBTree<K, pair<K, V>, MapKeyOfT> _t;
};

底层的红黑树不知道上层传的是map还是set,因此当需要进行两个结点键值的比较时,底层红黑树都会通过传入的仿函数来获取键值Key,进而进行两个结点键值的比较

set的仿函数不可缺

template<class K>
class set
{
	struct SetKeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	}
private:
	RBTree<K, K, SetKeyOfT> _t;

所以set容器传入底层红黑树的就是set的仿函数,map容器传入底层红黑树的就是map的仿函数

在这里插入图片描述

//查找函数
Node* Find(const K& key)
{
	Node* cur = _root;
	Node* parent = nullptr;
	while (cur)
	{
		if (kot(data) > kot(cur->_data))//待插入结点的key值大于当前结点的key值
		{
			//往节点的右子树走
			parent = cur;
			cur = cur->_right;
		}
		else if (kot(data) < kot(cur->_data))//待插入结点的key值小于当前结点的key值
		{
			//往节点的左子树走
			parent = cur;
			cur = cur->_left;
		}
		else//插入的值等于当前的节点,返回失败
		{
			return false;
		}
	}

注意

  • 1️⃣所有进行节点键值比较的地方,均需要通过仿函数获取对应节点的键值后再进行键值的比较
  • 2️⃣map和set的底层是一棵泛型红黑树实例化而来实际上不是同一棵红黑树

五. 迭代器实现

🎨正向迭代器

红黑树的正向迭代器实际上就是对结点指针进行了封装,因此在正向迭代器当中实际上就只有一个成员变量,那就是结点的指针!

//正向迭代器
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)
{}

当我们对正向迭代器进行解引用操作时,我们直接返回对应结点数据的引用即可

Ref operator*()
{
	return _node->_data;//返回节点数据的引用
}

以及成员访问操作符 ->

Ptr operator->()
{
	return &_node->_data;//返回节点数据的指针
}

当然,正向迭代器当中至少还需要重载==!=运算符,实现时直接判断两个迭代器所封装的结点是否是同一个即可

bool operator!=(const Self& s) const
{
	return _node != s._node//判断两个正向迭代器所封装的结点是否是同一个
}

bool operator==(const Self& s) const
{
	return _node == s._node//同上
}

重头戏才刚刚开始!真正的难点实际上++--运算符的重载

在这里插入图片描述

一个结点的正向迭代器进行++操作后,应该根据红黑树中序遍历的序列找到当前结点的下一个结点

具体思路如下:

  1. 当前节点的右子树不为空++就是找 右子树中序的第一个(最左节点)
  2. 如果节点的右子树为空++就是找到 孩子在祖先左边的祖先
Self& operator++()
{
	if (_node->_right)
	{
		//寻找该节点右子树中的最左节点
		Node* left = _node->_right;
		while (left->_left)
		{
			left = left->_left;
		}
		_node = left;//给给变成该节点
	}
	else
	{
		//找孩子在祖先左边的祖先
		Node* parent = _node->_parent;
		Node* cur = _node;
		while (parent && cur == parent->_right) //判断parent不为空,空就崩了
		{
			cur = cur->_parent;
			parent = parent->_parent;
		}
		_node = parent;
	}
	return *this;
}

同理, -- 的逻辑是一样的:

  1. 当前节点的左子树不为空--就是找 左子树中序的第一个(最右节点)
  2. 如果节点的左子树为空--就是找到 孩子在祖先右边的祖先
Self& operator--()
{
	if (_node->_left)//结点的左子树不为空
	{
		//寻找该节点左子树中的最右节点
		Node* right = _node->_left;
		while (right->_right)
		{
			right = right->_right;
		}
		_node = right;//给给变成该节点
	}
	else//结点的左子树为空
	{
		//找孩子在祖先右边的祖先
		Node* parent = _node->_parent;
		Node* cur = _node;
		while (parent && cur == parent->_left) //判断parent不为空,空就崩了
		{
			cur = cur->_parent;
			parent = parent->_parent;
		}
		_node = parent;
	}
	return *this;
}

我们实现迭代器的时候会将迭代器类型进行 typedef 方便调用,完事了不要忘了迭代器还有两个成员函数 begin()end()

  • begin() 返回中序序列当中第一个结点的正向迭代器,即最左节点
  • end ()返回中序序列当中最后一个结点下一个位置的正向迭代器,这里直接用空指针构造一个正向迭代器(不严谨的处理)
template<class K, class T, class KeyOfT>
struct RBTree
{
	typedef RBTreeNode<T> Node;
public:
	typedef __RBTreeIterator<T, T&, T*> iterator;//正向迭代器

	iterator begin()
	{
		//寻找最左节点
		Node* left = _root;
		while (left && left->_left)
		{
			left = left->_left;
		}

		//返回最左结点的正向迭代器
		return iterator(left);
	}

	iterator end()
	{
		//返回空节点的迭代器
		return iterator(nullptr);
	}
}

实际上,上述所实现的迭代器是有缺陷的,因为理论上我们对end()位置的正向迭代器进行–操作后,应该得到最后一个结点的正向迭代器,但我们实现end()时,是直接返回由nullptr构造得到的正向迭代器的,因此上述实现的代码无法完成此操作

所以我们不妨看看 C++ STL 库的实现逻辑

在这里插入图片描述库里面是采用了类似双向链表的处理,给整个红黑树造了一个哨兵位节点,该节点左边指向最小的最左节点,右边指向最大的右节点,同时还有一个非常 bug 的设计就是这里哨兵位节点 header 的红黑树头结点之间的 parent 相互指向

在该结构下,实现 begin() 时,直接用头结点的左孩子构造一个正向迭代器即可,实现 rbegin() 时,直接用头结点的右孩子构造一个反向迭代器即可,严谨的过程是先构造正向迭代器,再用正向迭代器构造反向迭代器,end() 和 rend() 此时就不需要什么 nullptr 了,直接有头结点(哨兵位)进行迭代器构造即可,这样就能完成一个逻辑完整的迭代器了

🎨反向迭代器

上面得知:反向迭代器的严谨构造过程是用正向迭代器进行封装,我们可以将

template<class Iterator>//迭代器适配器
struct ReverseIterator
{
	typedef ReverseIterator<Iterator> Self; //反向迭代器
	typedef typename Iterator::reference Ref; //指针的引用
	typedef typename Iterator::pointer Ptr; //结点指针

	Iterator _it; //反向迭代器封装的正向迭代器

	//构造函数
	ReverseIterator(Iterator it)
		:_it(it) //根据所给正向迭代器构造一个反向迭代器
	{}

	Ref operator*()
	{
		return *_it; //调用正向迭代器的operator*返回引用
	}
	
	Ptr operator->()
	{
		return _it.operator->(); //调用正向迭代器的operator->返回指针
	}

	Self& operator++() //前置++
	{
		--_it; //调用正向迭代器的前置--
		return *this;
	}
	//前置--
	Self& operator--()
	{
		++_it; //调用正向迭代器的前置++
		return *this;
	}

	bool operator!=(const Self& s) const
	{
		return _it != s._it; 
	}
	bool operator==(const Self& s) const
	{
		return _it == s._it; 
	}
};

Set的实现

都是接上红黑树的接口即可

namespace ljj
{
	template<class K>
	class set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:

		//typename告诉编译器这一大坨是类型,不是静态变量
		typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator;

		iterator begin()
		{
			return _t.begin();
		}

		iterator end()
		{
			return _t.end();
		}

		pair<iterator, bool> insert(const K& key)
		{
			return _t.Insert(key);//调用红黑树的insert
		}

	private:
		RBTree<K, K, SetKeyOfT> _t;
	};
}

Map的实现

map 也和 set 同理,复用红黑树的底层接口实现,此外还需要实现 [] 运算符的重载

template<class K, class V>
class map
{
	struct MapKeyOfT
	{
		const K& operator()(const pair<K, V>& kv)
		{
			return kv.first;
		}
	};
public:
	//typename告诉编译器这一大坨是类型,不是静态变量
	typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;

	iterator begin()
	{
		return _t.begin();
	}

	iterator end()
	{
		return _t.end();
	}

	pair<iterator, bool> insert(const pair<K, V>& kv)
	{
		return _t.Insert(kv);//调用红黑树的insert
	}

	//【】的底层调用就是Insert
	V& operator[](const K& key)
	{
		pair<iterator, bool> ret = Insert(make_pair(K, V()));//插入成功就是当前的迭代器,失败就是之前的迭代器
		return ret.first->second;
	}
private:
	RBTree<K, pair<K, V>, MapKeyOfT> _t;
};

红黑树的代码

//枚举颜色
enum  Colour
{
	RED,
	BLACK
};

template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;//三叉链
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;

	//pair<K, V> _kv;//存储键值对
	T _data;
	Colour _col;//节点颜色

	RBTreeNode(const T& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(RED)
	{}
};

//正向迭代器
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)
	{}

	Ref operator*()
	{
		return _node->_data;//返回节点数据的引用
	}

	Ptr operator->()
	{
		return &_node->_data;//返回节点数据的指针
	}

	bool operator!=(const Self& s) const
	{
		return _node != s._node;//判断两个正向迭代器所封装的结点是否是同一个
	}

	bool operator==(const Self& s) const
	{
		return _node == s._node;//同上
	}

	Self& operator++()
	{
		if (_node->_right)
		{
			//寻找该节点右子树中的最左节点
			Node* left = _node->_right;
			while (left->_left)
			{
				left = left->_left;
			}
			_node = left;//给给变成该节点
		}
		else
		{
			//找孩子在祖先左边的祖先
			Node* parent = _node->_parent;
			Node* cur = _node;
			while (parent && cur == parent->_right) //判断parent不为空,空就崩了
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}

	Self& operator--()
	{
		if (_node->_left)//结点的左子树不为空
		{
			//寻找该节点左子树中的最右节点
			Node* right = _node->_left;
			while (right->_right)
			{
				right = right->_right;
			}
			_node = right;//给给变成该节点
		}
		else//结点的左子树为空
		{
			//找孩子在祖先右边的祖先
			Node* parent = _node->_parent;
			Node* cur = _node;
			while (parent && cur == parent->_left) //判断parent不为空,空就崩了
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}
};


template<class K, class T, class KeyOfT>
struct RBTree
{
	typedef RBTreeNode<T> Node;
public:
	typedef __RBTreeIterator<T, T&, T*> iterator;//正向迭代器

	iterator begin()
	{
		//寻找最左节点
		Node* left = _root;
		while (left && left->_left)
		{
			left = left->_left;
		}

		//返回最左结点的正向迭代器
		return iterator(left);
	}

	iterator end()
	{
		//返回空节点的迭代器
		return iterator(nullptr);
	}

	//如果是空树,则插入节点作为root节点
	pair<iterator, bool> Insert(const T& data)
	{
		KeyOfT kot;
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;//根节点必须是黑色
			return make_pair(iterator(_root), true); //插入成功
		}

		//按二叉搜索树的插入方法,找到待插入位置
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (kot(data) > kot(cur->_data))//待插入结点的key值大于当前结点的key值
			{
				//往节点的右子树走
				parent = cur;
				cur = cur->_right;
			}
			else if (kot(data) < kot(cur->_data))//待插入结点的key值小于当前结点的key值
			{
				//往节点的左子树走
				parent = cur;
				cur = cur->_left;
			}
			else//插入的值等于当前的节点,返回失败
			{
				return make_pair(iterator(cur), false);
			}
		}

		//将节点链接到树上
		cur = new Node(data);//构造节点 
		Node* newnode = cur;
		cur->_col = RED;

		if (kot(data) < kot(parent->_data))		//判断链接左还是右?
		{
			//插入到左边
			parent->_left = cur;
			cur->_parent = parent;
		}
		else if (kot(data) > kot(parent->_data))
		{
			//插入到右边
			parent->_right = cur;
			cur->_parent = parent;
		}

		//如果插入节点的父节点是红色的,则需要对红黑树进行操作
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			assert(grandfather);
			assert(grandfather->_col == BLACK);

			//关键看叔叔  ~ 判断叔叔的位置
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				//情况1:uncle存在且为红  + 继续往上处理
				if (uncle && uncle->_col == RED)
				{
					//变色:p和u变黑,g变红
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续往上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else  //情况2 + 情况3:uncle不存在 + uncle存在且为黑
				{
					//情况二:单旋 + 变色
					//    g
					//  p   u
					//c            
					if (cur = parent->_left)
					{
						RotateR(grandfather);//右旋

						//颜色调整
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else//cur == parent->_right
					{
						//情况三:左右双旋 + 变色
						//    g
						//  p   u
						//    c 
						RotateL(parent);
						RotateR(grandfather);

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

					//继续往上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else  //情况2 + 情况3:uncle不存在 + uncle存在且为黑
				{
					//情况二:单旋 + 变色
					//    g
					//  u   p
					//        c            
					if (cur = parent->_right)
					{
						RotateL(grandfather);//左单 旋

						//颜色调整
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else//cur == parent->_left
					{
						//情况三:右左双旋 + 变色
						//    g
						//  u   p
						//    c 
						RotateR(parent);
						RotateL(grandfather);

						//调整颜色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
		}
		_root->_col = BLACK;//不管什么,最后根要变黑
		return make_pair(iterator(newnode), true);
	}

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

	bool IsBalance()
	{
		if (_root == nullptr)
		{
			return true;
		}

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

		// 黑色节点数量基准值
		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				++benchmark;

			cur = cur->_left;//以最左的路径进行
		}

		return PrevCheck(_root, 0, benchmark);
	}


private:
	bool PrevCheck(Node* root, int blackNum, int& benchmark)
	{
		if (root == nullptr)
		{
			//cout << blackNum << endl;
			//return;
			if (benchmark == 0)
			{
				benchmark = blackNum;
				return true;
			}

			if (blackNum != benchmark)
			{
				cout << "某条黑色节点的数量不相等" << endl;
				return false;
			}
			else
			{
				return true;
			}
		}

		if (root->_col == BLACK)
		{
			++blackNum;
		}
		//检测它的父亲
		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << "存在连续的红色节点" << endl;
			return false;
		}

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

	void _InOrder(Node* root)
	{
		if (root == nullptr)//空树也是红黑树
			return;
		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}
	//左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* parentParent = parent->_parent;

		//建立subRL与parent之间的联系
		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		//建立parent与subR之间的联系
		subR->_left = parent;
		parent->_parent = subR;

		//建立subR与parentParent之间的联系
		if (parentParent == nullptr)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (parent == parentParent->_left)
			{
				parentParent->_left = subR;
			}
			else
			{
				parentParent->_right = subR;
			}
			subR->_parent = parentParent;
		}
	}

	//右单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* parentParent = parent->_parent;

		//建立subLR与parent之间的联系
		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		//建立parent与subL之间的联系
		subL->_right = parent;
		parent->_parent = subL;

		//建立subL与parentParent之间的联系
		if (parentParent == nullptr)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (parent == parentParent->_left)
			{
				parentParent->_left = subL;
			}
			else
			{
				parentParent->_right = subL;
			}
			subL->_parent = parentParent;
		}
	}

	//左右双旋
	void RotateLR(Node* parent)
	{
		RotateL(parent->_left);
		RotateR(parent);
	}

	//右左双旋
	void RotateRL(Node* parent)
	{
		RotateR(parent->_right);
		RotateL(parent);
	}

private:
	Node* _root = nullptr;
};

📢写在最后

请添加图片描述

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

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

相关文章

蓝桥杯Python组排列和组合、二进制讲解

目录 一、排列 1、Python 的排列函数 permutations() 2、permutations() 按什么顺序输出序列&#xff08;重要⭐&#xff09; 3、易错点 二、组合 1、Python的组合函数combinations() 2、注意内容 三、手写排列和组合代码 1、手写排列代码&#xff08;暴力法&#xff…

【PWA学习】2. 使用 Manifest, 让你的 WebApp 更 Native

引言 我们知道&#xff0c;在 chrome(等一些现代浏览器)中&#xff0c;你可以将访问的网站添加到桌面&#xff0c;这样就会在桌面生成一个类似 “快捷方式” 的图标&#xff0c;当你点击该图标时&#xff0c;便可以快速访问该网站(Web App) 我们以 demo 为例&#xff0c;其添加…

无监督聚类表征学习方法之对比学习(Contrastive Learning)——simclr方法

无监督聚类表征学习方法之对比学习(Contrastive Learning)——simclr方法 1.参考论文 《A Simple Framework for Contrastive Learning of Visual Representations》 2.无监督聚类表征学习方法 主要有几种&#xff1a; ①自动编码器(AutoEncoder,AE); ②变分自编码器(Variatio…

两款开源.NET工作流引擎 Elsa 与ccflow使用比较

相对java开源的工作流程引擎.net开源的工作流程引擎相对较少&#xff0c;这里整理两款.net开源工作流引擎&#xff0c;做一下对比使用。elsa示例代码:Githubd地址&#xff1a;https://github.com/zhenl/MyElsaccflow下载地址&#xff1a;https://gitee.com/opencc/ccflowCCFlow…

Java笔记021-异常-Exception

异常-Exception看个实际问题和一段代码运行下面的代码&#xff0c;看看有什么问题->引出异常和异常处理机制package com12.exception_;/*** author 甲柒* version 1.0* title Exception01* package com12.exception_* time 2023/1/9 14:38*/ public class Exception01 {publ…

Mask RCNN网络源码解读(Ⅳ) --- Mask R-CNN论文解读

目录 1.Mask R-CNN简介 2.Mask分支 3.Mask R-CNN损失 4Mask分支预测使用 1.Mask R-CNN简介 回顾我们之前所说的图像分类、目标检测、语义分割的内容&#xff1a; 我们来看一下实例分割和语义分割的差别&#xff1a; Mask R-CNN不仅能够同时进行目标检测与分割&#xff0c;…

查找算法之二分查找

目录 二分查找 算法实现 “双闭区间”实现 算法实现 python C 两种表示对比 大数越界处理 优点与缺点 二分查找 二分查找&#xff0c;利用数据的有序性&#xff0c;通过每轮缩小一半搜索区间来查找目标元素。 使用二分查找有两个前置条件&#xff1a; 要求输入数据…

如何在GitLab上传本地项目

上传前需准备&#xff1a;需要安装Git&#xff0c;点击进入官网下载&#xff1a;Git 在本地上传GitLab项目的步骤目录介绍&#xff1a; 一、配置SSH秘钥&#xff08;仅针对本机首次上传GitLab项目&#xff09; 二、上传项目 1、新建一个空文件夹&#xff0c;并在该文件夹下右键…

Deque

Deque&#xff1a; “double ended queue&#xff08;双端队列&#xff09;”的缩写&#xff0c;通常读为“deck”&#xff1b; Deque是一个线性集合&#xff0c;支持在两端插入和移除元素。 Deque有三种用途&#xff1a; 双端队列(两端都可进出) Deque< Integer> de…

机器学习实战教程(十三):树回归基础篇

一、前言本篇文章将会讲解CART算法的实现和树的剪枝方法&#xff0c;通过测试不同的数据集&#xff0c;学习CART算法和树剪枝技术。二、将CART&#xff08;Classification And Regression Trees&#xff09;算法用于回归在之前的文章&#xff0c;我们学习了决策树的原理和代码实…

成功上岸字节全靠这份Redis技术笔记,深入浅出值得一看

前言 正如标题所说&#xff0c;我现在已经如愿以偿地进了字节&#xff01;之前自己一直待在一个不大不小的外包公司&#xff0c;每天做着重复的层删改查工作。直到22年年底&#xff0c;自己通过朋友的介绍拿到了字节的面试机会&#xff0c;自己在家复习了3个月&#xff0c;成功…

decltype类型指示符

decltype类型指示符一、什么是decltype类型指示符二、typeid运算符三、使用decltype指示符四、decltype和引用五、decltype(auto)六、本章代码汇总一、什么是decltype类型指示符 有时会遇到这种情况&#xff1a;希望从表达式的类型推断出要定义的变量的类型&#xff0c;但是不…

超实用的实用Shell脚本

一、Dos 攻击防范&#xff08;自动屏蔽攻击 IP&#xff09; 代码&#xff1a; #!/bin/bash DATE$(date %d/%b/%Y:%H:%M) LOG_FILE/usr/local/nginx/logs/demo2.access.log ABNORMAL_IP$(tail -n5000 $LOG_FILE |grep $DATE |awk {a[$1]}END{for(i in a)if(a[i]>10)print…

Spring 学习笔记2

1.spring设置JDBC链接池 classpath:jdbc.properties是有多个连接池时的写法&#xff0c;一般都用这种 还有就是配置文件里不要直接使用username&#xff0c;会被覆盖 使用${}来从文件里读取属性 <beans xmlns"http://www.springframework.org/schema/beans"xmlns…

bitmap原理+性能优化实践

目录 背景 总体结构 从RoaringBitmp说起 3.1arraycontainer 1.3.2 bitmapcontainer 1.3.3 runcontainer 上源码 Roaring64NavigableMap RoaringBitmap RoaringArray 三种Container ArrayContainer BitmapContainer RunContainer 工作应用 需求 分析 能否多线…

ArcGIS基础实验操作100例--实验75气体扩散空间分析

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 高级编辑篇--实验75 气体扩散空间分析 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;1&…

MySQL常用基础 - 小白必看(二)

MySQL数据库基本操作 一、DDL 概念&#xff1a;是一个数据定义语言 该语言部分包括&#xff1a; 1、对数据库的常用操作 创建数据库&#xff1a; 1、create database 数据库名 (直接删除) 2、create database if not exists 数据库名 &#xff08;判断数据库是否存在&…

视频的水印怎样去掉?这些去水印的方法值得你试试看

喜欢视频剪辑的你会不会经常遇到这种情况&#xff1a;每次上网查找的视频素材&#xff0c;保存下来后总是带有一些水印&#xff0c;这些水印不仅不够美观&#xff0c;而且还会遮挡住视频的一些部分&#xff0c;实在是烦人。如果你遇到这种情况&#xff0c;会很想知道“给视频无…

86、【栈与队列】leetcode ——39. 滑动窗口最大值:单调队列+滑动窗口(C++版本)

题目描述 239. 滑动窗口最大值 一、单调队列滑动窗口方法 本题的特点是维护一个窗口&#xff0c;在窗口不断向前移动时&#xff0c;获取其中的最大值。由于窗口在向前移动过程中&#xff0c;元素存在着进入和出去的连续顺序&#xff0c;与FIFO的特点类似。 故可考虑用队列实…

【数据结构】初识数据结构,十分钟带你玩转算法复杂度

目录 &#x1f34a;前言&#x1f34a;&#xff1a; &#x1f95d;一、初识数据结构&#x1f95d;&#xff1a; 1.数据结构&#xff1a; 2.算法&#xff1a; &#x1f353;二、算法效率&#x1f353;&#xff1a; &#x1f348;三、算法复杂度&#x1f348;&#xff1a; 1.时…