C++进阶 红黑树封装map和set

news2024/11/23 8:31:19

作者:@小萌新
专栏:@C++进阶
作者简介:大二学生 希望能和大家一起进步!
本篇博客简介:使用红黑树封装出map和set

红黑树封装map和set

  • 红黑树源代码
  • 红黑树模板参数的控制
  • 红黑树结点当中存储的数据
  • 模板参数仿函数的增加
  • 正向迭代器的实现
    • 框架
    • 构造函数
    • 解引用
    • 箭头操作
    • 等于和不等于
    • ++操作
    • - -操作
  • 反向迭代器的实现
  • 封装后的代码
    • 红黑树
    • 正向迭代器
    • 反向迭代器
    • set
    • map

红黑树源代码

我们下面会对KV模型的红黑树进行封装 封装出STL库中的map和set

代码如下

//枚举定义结点的颜色
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;

	//结点的颜色
	int _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()
		:_root(nullptr)
	{}

	//拷贝构造
	RBTree(const RBTree<K, V>& t)
	{
		_root = _Copy(t._root, nullptr);
	}

	//赋值运算符重载(现代写法)
	RBTree<K, V>& operator=(RBTree<K, V> t)
	{
		swap(_root, t._root);
		return *this;
	}

	//析构函数
	~RBTree()
	{
		_Destroy(_root);
		_root = nullptr;
	}

	//查找函数
	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (key < cur->_kv.first) //key值小于该结点的值
			{
				cur = cur->_left; //在该结点的左子树当中查找
			}
			else if (key > cur->_kv.first) //key值大于该结点的值
			{
				cur = cur->_right; //在该结点的右子树当中查找
			}
			else //找到了目标结点
			{
				return cur; //返回该结点
			}
		}
		return nullptr; //查找失败
	}

	//插入函数
	pair<Node*, bool> Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr) //若红黑树为空树,则插入结点直接作为根结点
		{
			_root = new Node(kv);
			_root->_col = BLACK; //根结点必须是黑色
			return make_pair(_root, true); //插入成功
		}
		//1、按二叉搜索树的插入方法,找到待插入位置
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (kv.first < cur->_kv.first) //待插入结点的key值小于当前结点的key值
			{
				//往该结点的左子树走
				parent = cur;
				cur = cur->_left;
			}
			else if (kv.first > cur->_kv.first) //待插入结点的key值大于当前结点的key值
			{
				//往该结点的右子树走
				parent = cur;
				cur = cur->_right;
			}
			else //待插入结点的key值等于当前结点的key值
			{
				return make_pair(cur, false); //插入失败
			}
		}

		//2、将待插入结点插入到树中
		cur = new Node(kv); //根据所给值构造一个结点
		Node* newnode = cur; //记录新插入的结点(便于后序返回)
		if (kv.first < parent->_kv.first) //新结点的key值小于parent的key值
		{
			//插入到parent的左边
			parent->_left = cur;
			cur->_parent = parent;
		}
		else //新结点的key值大于parent的key值
		{
			//插入到parent的右边
			parent->_right = cur;
			cur->_parent = parent;
		}

		//3、若插入结点的父结点是红色的,则需要对红黑树进行调整
		while (parent&&parent->_col == RED)
		{
			Node* grandfather = parent->_parent; //parent是红色,则其父结点一定存在
			if (parent == grandfather->_left) //parent是grandfather的左孩子
			{
				Node* uncle = grandfather->_right; //uncle是grandfather的右孩子
				if (uncle&&uncle->_col == RED) //情况1:uncle存在且为红
				{
					//颜色调整
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else //情况2+情况3:uncle不存在 + uncle存在且为黑
				{
					if (cur == parent->_left)
					{
						RotateR(grandfather); //右单旋

						//颜色调整
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else //cur == parent->_right
					{
						RotateLR(grandfather); //左右双旋

						//颜色调整
						grandfather->_col = RED;
						cur->_col = BLACK;
					}
					break; //子树旋转后,该子树的根变成了黑色,无需继续往上进行处理
				}
			}
			else //parent是grandfather的右孩子
			{
				Node* uncle = grandfather->_left; //uncle是grandfather的左孩子
				if (uncle&&uncle->_col == RED) //情况1:uncle存在且为红
				{
					//颜色调整
					uncle->_col = parent->_col = BLACK;
					grandfather->_col = RED;

					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else //情况2+情况3:uncle不存在 + uncle存在且为黑
				{
					if (cur == parent->_left)
					{
						RotateRL(grandfather); //右左双旋

						//颜色调整
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					else //cur == parent->_right
					{
						RotateL(grandfather); //左单旋

						//颜色调整
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					break; //子树旋转后,该子树的根变成了黑色,无需继续往上进行处理
				}
			}
		}
		_root->_col = BLACK; //根结点的颜色为黑色(可能被情况一变成了红色,需要变回黑色)
		return make_pair(newnode, true); //插入成功
	}

	//删除函数
	bool Erase(const K& key)
	{
		//用于遍历二叉树
		Node* parent = nullptr;
		Node* cur = _root;
		//用于标记实际的待删除结点及其父结点
		Node* delParentPos = nullptr;
		Node* delPos = nullptr;
		while (cur)
		{
			if (key < cur->_kv.first) //所给key值小于当前结点的key值
			{
				//往该结点的左子树走
				parent = cur;
				cur = cur->_left;
			}
			else if (key > cur->_kv.first) //所给key值大于当前结点的key值
			{
				//往该结点的右子树走
				parent = cur;
				cur = cur->_right;
			}
			else //找到了待删除结点
			{
				if (cur->_left == nullptr) //待删除结点的左子树为空
				{
					if (cur == _root) //待删除结点是根结点
					{
						_root = _root->_right; //让根结点的右子树作为新的根结点
						if (_root)
						{
							_root->_parent = nullptr;
							_root->_col = BLACK; //根结点为黑色
						}
						delete cur; //删除原根结点
						return true;
					}
					else
					{
						delParentPos = parent; //标记实际删除结点的父结点
						delPos = cur; //标记实际删除的结点
					}
					break; //进行红黑树的调整以及结点的实际删除
				}
				else if (cur->_right == nullptr) //待删除结点的右子树为空
				{
					if (cur == _root) //待删除结点是根结点
					{
						_root = _root->_left; //让根结点的左子树作为新的根结点
						if (_root)
						{
							_root->_parent = nullptr;
							_root->_col = BLACK; //根结点为黑色
						}
						delete cur; //删除原根结点
						return true;
					}
					else
					{
						delParentPos = parent; //标记实际删除结点的父结点
						delPos = cur; //标记实际删除的结点
					}
					break; //进行红黑树的调整以及结点的实际删除
				}
				else //待删除结点的左右子树均不为空
				{
					//替换法删除
					//寻找待删除结点右子树当中key值最小的结点作为实际删除结点
					Node* minParent = cur;
					Node* minRight = cur->_right;
					while (minRight->_left)
					{
						minParent = minRight;
						minRight = minRight->_left;
					}
					cur->_kv.first = minRight->_kv.first; //将待删除结点的key改为minRight的key
					cur->_kv.second = minRight->_kv.second; //将待删除结点的value改为minRight的value
					delParentPos = minParent; //标记实际删除结点的父结点
					delPos = minRight; //标记实际删除的结点
					break; //进行红黑树的调整以及结点的实际删除
				}
			}
		}
		if (delPos == nullptr) //delPos没有被修改过,说明没有找到待删除结点
		{
			return false;
		}

		//记录待删除结点及其父结点(用于后续实际删除)
		Node* del = delPos;
		Node* delP = delParentPos;

		//调整红黑树
		if (delPos->_col == BLACK) //删除的是黑色结点
		{
			if (delPos->_left) //待删除结点有一个红色的左孩子(不可能是黑色)
			{
				delPos->_left->_col = BLACK; //将这个红色的左孩子变黑即可
			}
			else if (delPos->_right) //待删除结点有一个红色的右孩子(不可能是黑色)
			{
				delPos->_right->_col = BLACK; //将这个红色的右孩子变黑即可
			}
			else //待删除结点的左右均为空
			{
				while (delPos != _root) //可能一直调整到根结点
				{
					if (delPos == delParentPos->_left) //待删除结点是其父结点的左孩子
					{
						Node* brother = delParentPos->_right; //兄弟结点是其父结点的右孩子
						//情况一:brother为红色
						if (brother->_col == RED)
						{
							delParentPos->_col = RED;
							brother->_col = BLACK;
							RotateL(delParentPos);
							//需要继续处理
							brother = delParentPos->_right; //更新brother(否则在本循环中执行其他情况的代码会出错)
						}
						//情况二:brother为黑色,且其左右孩子都是黑色结点或为空
						if (((brother->_left == nullptr) || (brother->_left->_col == BLACK))
							&& ((brother->_right == nullptr) || (brother->_right->_col == BLACK)))
						{
							brother->_col = RED;
							if (delParentPos->_col == RED)
							{
								delParentPos->_col = BLACK;
								break;
							}
							//需要继续处理
							delPos = delParentPos;
							delParentPos = delPos->_parent;
						}
						else
						{
							//情况三:brother为黑色,且其左孩子是红色结点,右孩子是黑色结点或为空
							if ((brother->_right == nullptr) || (brother->_right->_col == BLACK))
							{
								brother->_left->_col = BLACK;
								brother->_col = RED;
								RotateR(brother);
								//需要继续处理
								brother = delParentPos->_right; //更新brother(否则执行下面情况四的代码会出错)
							}
							//情况四:brother为黑色,且其右孩子是红色结点
							brother->_col = delParentPos->_col;
							delParentPos->_col = BLACK;
							brother->_right->_col = BLACK;
							RotateL(delParentPos);
							break; //情况四执行完毕后调整一定结束
						}
					}
					else //delPos == delParentPos->_right //待删除结点是其父结点的左孩子
					{
						Node* brother = delParentPos->_left; //兄弟结点是其父结点的左孩子
						//情况一:brother为红色
						if (brother->_col == RED) //brother为红色
						{
							delParentPos->_col = RED;
							brother->_col = BLACK;
							RotateR(delParentPos);
							//需要继续处理
							brother = delParentPos->_left; //更新brother(否则在本循环中执行其他情况的代码会出错)
						}
						//情况二:brother为黑色,且其左右孩子都是黑色结点或为空
						if (((brother->_left == nullptr) || (brother->_left->_col == BLACK))
							&& ((brother->_right == nullptr) || (brother->_right->_col == BLACK)))
						{
							brother->_col = RED;
							if (delParentPos->_col == RED)
							{
								delParentPos->_col = BLACK;
								break;
							}
							//需要继续处理
							delPos = delParentPos;
							delParentPos = delPos->_parent;
						}
						else
						{
							//情况三:brother为黑色,且其右孩子是红色结点,左孩子是黑色结点或为空
							if ((brother->_left == nullptr) || (brother->_left->_col == BLACK))
							{
								brother->_right->_col = BLACK;
								brother->_col = RED;
								RotateL(brother);
								//需要继续处理
								brother = delParentPos->_left; //更新brother(否则执行下面情况四的代码会出错)
							}
							//情况四:brother为黑色,且其左孩子是红色结点
							brother->_col = delParentPos->_col;
							delParentPos->_col = BLACK;
							brother->_left->_col = BLACK;
							RotateR(delParentPos);
							break; //情况四执行完毕后调整一定结束
						}
					}
				}
			}
		}
		//进行实际删除
		if (del->_left == nullptr) //实际删除结点的左子树为空
		{
			if (del == delP->_left) //实际删除结点是其父结点的左孩子
			{
				delP->_left = del->_right;
				if (del->_right)
					del->_right->_parent = delP;
			}
			else //实际删除结点是其父结点的右孩子
			{
				delP->_right = del->_right;
				if (del->_right)
					del->_right->_parent = delP;
			}
		}
		else //实际删除结点的右子树为空
		{
			if (del == delP->_left) //实际删除结点是其父结点的左孩子
			{
				delP->_left = del->_left;
				if (del->_left)
					del->_left->_parent = delP;
			}
			else //实际删除结点是其父结点的右孩子
			{
				delP->_right = del->_left;
				if (del->_left)
					del->_left->_parent = delP;
			}
		}
		delete del; //实际删除结点
		return true;
	}
	
private:
	//拷贝树
	Node* _Copy(Node* root, Node* parent)
	{
		if (root == nullptr)
		{
			return nullptr;
		}
		Node* copyNode = new Node(root->_data);
		copyNode->_parent = parent;
		copyNode->_left = _Copy(root->_left, copyNode);
		copyNode->_right = _Copy(root->_right, copyNode);
		return copyNode;
	}

	//析构函数子函数
	void _Destroy(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}

	//左单旋
	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);
	}

	Node* _root; //红黑树的根结点
};

红黑树模板参数的控制

我们都知道

  • set是K模型的容器
  • map是KV模型的容器

那么我们应该如何使用一颗KV模型的红黑树来同时实现它们呢?

这里就涉及到模板参数控制的一些技巧

假设我们控制红黑树的模板是这样子的

template<class Key, class Value>
class RBTree

我们了解的是 value的模板参数可以只是value的键值

也可以是key和value的键值对

那么对于set来说 它传给红黑树底层的参数就是两个key组成的键值对

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

而对于map容器来说 它传给红黑树底层的参数则是key和value的键值对

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

那么这里就会出现一个问题

对于map和set来说 能不能不要红黑树的第一个模板参数 只要第二个模板参数呢?

回答问题

  • 对于set来说可以 因为set传入红黑树的两个参数都是相同的
  • 对于map来说不可以 因为map的某些接口函数只要求给出键值Key 而不是键值对

红黑树结点当中存储的数据

我们首先来看红黑树节点的代码

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

	//存储的键值对
	pair<K, V> _kv;

	//结点的颜色
	int _col; //红/黑

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

我们可以看到 这里面存储的数据是一个键值对

那么对于我们的map和set来说 需要存储的数据是什么呢?

  • 对于set来说 因为key和value的值是一样子的 所以存储哪个都可以
  • 对于map来说 必须要存储value里的键值对

综上 我们只需要在红黑树中存储value就可以了

在这里插入图片描述
更新后的红黑树节点代码如下

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

	//存储的数据
	Value _data;

	//结点的颜色
	int _col; //红/黑

	//构造函数
	RBTreeNode(const Value& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(RED)
	{}
};

模板参数仿函数的增加

在红黑树的增删查改过程中我们都需要比较大小

  • 对于set来说 比较大小使用的是key值进行比较
  • 对于map来说 比较大小使用的是键值对的key值进行比较

这里也就抛出了一个问题

在这里插入图片描述
我们前面通过键值对的first来比较就有点不合适了 因为我们不知道上层容器是map和set

到底传入的是不是一个键值对

那么如何才能解决这个问题呢?

那这里就用到我们以前学过的一个知识了 仿函数

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

首先讲解思路 既然我们不确定上层容器是什么 那么我们就让它们多传入一个参数

用这个参数来标识它们的比较方式就好了

比如说这样子

template<class Key,class Value>
class map
{
	// 仿函数
	struct MapkeyOfValue
	{
		const K& operator()(const pair<Key, Value>& kv)
		{
			return kv.first;
		}
	};
public:
	// ...
private:
	RBTree<Key, pair<Key, Value>, MapKeyOfValue> _t;
};

有了这个标识符之后我们就解决了上面的不确定比较什么的问题

修改后的代码如下

//查找函数
iterator Find(const K& key)
{
	KeyOfT kot;
	Node* cur = _root;
	while (cur)
	{
		if (key < kot(cur->_data)) //key值小于该结点的值
		{
			cur = cur->_left; //在该结点的左子树当中查找
		}
		else if (key > kot(cur->_data)) //key值大于该结点的值
		{
			cur = cur->_right; //在该结点的右子树当中查找
		}
		else //找到了目标结点
		{
			return iterator(cur); //返回该结点
		}
	}
	return end(); //查找失败
}

当然为了保持结构的一致性 set也需要一个仿函数

template<class Key,class Value>
class set
{
	// 仿函数
	struct SetkeyOfValue
	{
		const K& operator()(const Key& k)
		{
			return k;
		}
	};
public:
	// ...
private:
	RBTree<Key, Key, SetKeyOfValue> _t;
};

需要注意的是 我们后面所有要比较大小的地方都需要用到仿函数

正向迭代器的实现

红黑树的迭代器实际上就是对其指针进行了封装

至于如何封装的我们下面来看具体实现

框架

template<class T, class Ref , class Ptr>
struct _TreeIterator
{
	typedef RBTreeNode<T> Node; //节点的类型
	typedef _TreeIterator<T, Ref, Ptr> Self; //正向迭代器的类型

	Node* _node; // 被封装的指针 
};

首先大概框架如上所示 因为是对指针的封装 所以我们只需要有一个指针类型的数据就可以

构造函数

我们前面说过了 红黑树的迭代器是对于指针的封装 所以说构造函数创建一个新指针就可以

代码标识如下

	_TreeIterator(Node* node)
		:_node(node)
	{}

解引用

当我们对迭代器使用解引用的时候 我们想得到的是得到这个迭代器对应的数据 所以我们只需要返回数据的引用就可以

	Ref operator* ()
	{
		return _node->_data;
	}

箭头操作

当我们对迭代器使用箭头操作的时候 我们想得到的是这个数据的指针 所以我们只需要返回数据的地址就可以

Ptr operator-> ()
	{
		return *(_node->_data);
	}

等于和不等于

我们判断两个迭代器是否相等 实际上就是判断两个指针是否相等

稍微转化一下就可以


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

	bool operator== (Self& s)
	{
		return _node == s._node;
	}

++操作

在这里插入图片描述
当我们使用++操作的时候 我们想找到的应该是按照中序遍历的顺序找到当前节点的下一个节点

这个操作实际上是比较难想的 我们先讲解如何操作再梳理下思路

操作规则如下

  1. 如果当前节点的右子树不为空 则找到其右子树的最左节点
  2. 如果当前节点的右子树为空 往上找到该节点不在右子树的祖先节点

为什么有规则一呢?

我们想想看 是不是二叉树的中序遍历实际上就是二叉树投影到平面上啊

那么是不是离自己最近的右边位置就是比自己大一的位置

按照这个思路想想 我们就能理解规则一了

为什么有规则二呢?

因为该节点的右子树不存在了 是不是就说明该节点在这颗子树下是最大的了

那么我们就需要往上找该子树不为右子树的那个父亲节点

(也是平面投影下最接近该节点的下一个位置的)

代码表示如下

	Self operator++()
	{
		if (_node->_right)
		{
			// 找到其右节点的最左节点 并返回迭代器
			Node* next = _node->_right;
			while (next->_left)
			{
				next = next->_left
			}
			_node = next;
		}
		else // 右子树为空
		{
			// 找到为左子树的那个祖先节点
			Node* cur = _node;
			Node* parent = cur->_parent;

			while (parent && cur == parent->_right) // 有可能不存在
			{
				cur = parent;
				parent = cur->_parent;
			}

			_node = parent;
		}
		return *this;
	}

- -操作

当我们使用–操作的时候 我们想找到的应该是按照中序遍历的顺序找到当前节点的上一个节点

和加加操作类似 我们先讲解如何操作再梳理下思路

操作规则如下

  1. 如果当前节点的左子树不为空 则找到其右子树的最左节点
  2. 如果当前节点的左子树为空 则往上找到该节点不在左子树的祖先节点

解释和++操作类似

规则一 对应着找中序遍历的上一个节点

如果左子树存在的话 那么左子树的最右节点就是我们要找的最近的上一个节点

规则二

该节点的左子树不存在了 自然就是这个子树的最小值 所以要往上找该子树为右子树的祖先节点

代码表示如下

	Self operator--()
	{
		// 如果左子树不为空
		if (_node->_left)
		{
			Node* prev = _node->_left;
			while (prev->_right)
			{
				prev = prev->_right; // 找到最右节点
			}
			_node = prev;
		}
		else // 左子树不存在的情况
		{
			Node* cur = _node;
			Node* parent = _node->_parent;
			// parent有可能不存在 
			while (parent && parent->_left == cur)
			{
				cur = parent;
				parent = parent->_parent;
			}

			_node = parent;
		}
		return *this;
	}

正向迭代器实现后 我们需要再红黑树内部对类型进行typedef

需要注意的是 为了能让外部能够使用迭代器 我们需要再public区域对其进行typedef

  • 对于begin迭代器来说 我们直接返回最小的那个节点就可以
  • 对于end迭代器 按理来说我们需要返回最大的那个节点后面一个的迭代器 但是我们这里直接用空迭代器代替

代码表示如下

template<class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node; //结点的类型
public:
	typedef __TreeIterator<T, T&, T*> iterator; //正向迭代器
	
	iterator begin()
	{
		//寻找最左结点
		Node* left = _root;
		while (left&&left->_left)
		{
			left = left->_left;
		}
		//返回最左结点的正向迭代器
		return iterator(left);
	}
	iterator end()
	{
		//返回由nullptr构造得到的正向迭代器(不严谨)
		return iterator(nullptr);
	}
private:
	Node* _root; //红黑树的根结点
};

但是实际上像我们上面这样做是有缺陷的 因为我们无法通过end迭代器–来找到上一个迭代器

在STL库中 给出了这样子的解决方案 它在根节点的上方插入了一个头节点

左右指针分别指向begin和end迭代器 这就很巧妙的解决了上面的问题
在这里插入图片描述

反向迭代器的实现

反向迭代器本质上就是对于正向迭代器的封装

所以说反向迭代器就是正向迭代器的迭代器适配器

我们可以直接写出下面的代码

//反向迭代器---迭代器适配器
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; //调用正向迭代器的operator!=
	}
	bool operator==(const Self& s) const
	{
		return _it == s._it; //调用正向迭代器的operator==
	}
};

之后还是一样 我们需要对于红黑树的内部typedef

  1. 对于rbegin迭代器来说 我们直接返回最大的那个节点就可以
  2. 对于rend迭代器来说 也是一样返回一个空迭代器
template<class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node; //结点的类型
public:
	typedef ReverseIterator<iterator> reverse_iterator; //反向迭代器
	
	reverse_iterator rbegin()
	{
		//寻找最右结点
		Node* right = _root;
		while (right&&right->_right)
		{
			right = right->_right;
		}
		//返回最右结点的反向迭代器
		return reverse_iterator(iterator(right));
	}
	reverse_iterator rend()
	{
		//返回由nullptr构造得到的反向迭代器(不严谨)
		return reverse_iterator(iterator(nullptr));
	}
private:
	Node* _root; //红黑树的根结点
};

封装后的代码

红黑树

//枚举定义结点的颜色
enum Colour
{
	RED,
	BLACK
};

//红黑树结点的定义
template<class T>
struct RBTreeNode
{
	//三叉链
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;

	//存储的数据
	T _data;

	//结点的颜色
	int _col; //红/黑

	//构造函数
	RBTreeNode(const T& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(RED)
	{}
};
//红黑树的实现
template<class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node; //结点的类型
public:
	typedef __TreeIterator<T, T&, T*> iterator; //正向迭代器
	typedef ReverseIterator<iterator> reverse_iterator; //反向迭代器

	reverse_iterator rbegin()
	{
		//寻找最右结点
		Node* right = _root;
		while (right&&right->_right)
		{
			right = right->_right;
		}
		//返回最右结点的反向迭代器
		return reverse_iterator(iterator(right));
	}
	reverse_iterator rend()
	{
		//返回由nullptr构造得到的反向迭代器(不严谨)
		return reverse_iterator(iterator(nullptr));
	}

	iterator begin()
	{
		//寻找最左结点
		Node* left = _root;
		while (left&&left->_left)
		{
			left = left->_left;
		}
		//返回最左结点的正向迭代器
		return iterator(left);
	}
	iterator end()
	{
		//返回由nullptr构造得到的正向迭代器(不严谨)
		return iterator(nullptr);
	}
	//构造函数
	RBTree()
		:_root(nullptr)
	{}

	//拷贝构造
	RBTree(const RBTree<K, T, KeyOfT>& t)
	{
		_root = _Copy(t._root, nullptr);
	}

	//赋值运算符重载(现代写法)
	RBTree<K, T, KeyOfT>& operator=(RBTree<K, T, KeyOfT> t)
	{
		swap(_root, t._root);
		return *this; //支持连续赋值
	}

	//析构函数
	~RBTree()
	{
		_Destroy(_root);
		_root = nullptr;
	}

	//查找函数
	iterator Find(const K& key)
	{
		KeyOfT kot;
		Node* cur = _root;
		while (cur)
		{
			if (key < kot(cur->_data)) //key值小于该结点的值
			{
				cur = cur->_left; //在该结点的左子树当中查找
			}
			else if (key > kot(cur->_data)) //key值大于该结点的值
			{
				cur = cur->_right; //在该结点的右子树当中查找
			}
			else //找到了目标结点
			{
				return iterator(cur); //返回该结点
			}
		}
		return end(); //查找失败
	}

	//插入函数
	pair<iterator, bool> Insert(const T& data)
	{
		if (_root == nullptr) //若红黑树为空树,则插入结点直接作为根结点
		{
			_root = new Node(data);
			_root->_col = BLACK; //根结点必须是黑色
			return make_pair(iterator(_root), true); //插入成功
		}
		//1、按二叉搜索树的插入方法,找到待插入位置
		KeyOfT kot;
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (kot(data) < kot(cur->_data)) //待插入结点的key值小于当前结点的key值
			{
				//往该结点的左子树走
				parent = cur;
				cur = cur->_left;
			}
			else if (kot(data) > kot(cur->_data)) //待插入结点的key值大于当前结点的key值
			{
				//往该结点的右子树走
				parent = cur;
				cur = cur->_right;
			}
			else //待插入结点的key值等于当前结点的key值
			{
				return make_pair(iterator(cur), false); //插入失败
			}
		}

		//2、将待插入结点插入到树中
		cur = new Node(data); //根据所给值构造一个结点
		Node* newnode = cur; //记录新插入的结点(便于后序返回)
		if (kot(data) < kot(parent->_data)) //新结点的key值小于parent的key值
		{
			//插入到parent的左边
			parent->_left = cur;
			cur->_parent = parent;
		}
		else //新结点的key值大于parent的key值
		{
			//插入到parent的右边
			parent->_right = cur;
			cur->_parent = parent;
		}

		//3、若插入结点的父结点是红色的,则需要对红黑树进行调整
		while (parent&&parent->_col == RED)
		{
			Node* grandfather = parent->_parent; //parent是红色,则其父结点一定存在
			if (parent == grandfather->_left) //parent是grandfather的左孩子
			{
				Node* uncle = grandfather->_right; //uncle是grandfather的右孩子
				if (uncle&&uncle->_col == RED) //情况1:uncle存在且为红
				{
					//颜色调整
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else //情况2+情况3:uncle不存在 + uncle存在且为黑
				{
					if (cur == parent->_left)
					{
						RotateR(grandfather); //右单旋

						//颜色调整
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else //cur == parent->_right
					{
						RotateLR(grandfather); //左右双旋

						//颜色调整
						grandfather->_col = RED;
						cur->_col = BLACK;
					}
					break; //子树旋转后,该子树的根变成了黑色,无需继续往上进行处理
				}
			}
			else //parent是grandfather的右孩子
			{
				Node* uncle = grandfather->_left; //uncle是grandfather的左孩子
				if (uncle&&uncle->_col == RED) //情况1:uncle存在且为红
				{
					//颜色调整
					uncle->_col = parent->_col = BLACK;
					grandfather->_col = RED;

					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else //情况2+情况3:uncle不存在 + uncle存在且为黑
				{
					if (cur == parent->_left)
					{
						RotateRL(grandfather); //右左双旋

						//颜色调整
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					else //cur == parent->_right
					{
						RotateL(grandfather); //左单旋

						//颜色调整
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					break; //子树旋转后,该子树的根变成了黑色,无需继续往上进行处理
				}
			}
		}
		_root->_col = BLACK; //根结点的颜色为黑色(可能被情况一变成了红色,需要变回黑色)
		return make_pair(iterator(newnode), true); //插入成功
	}

	//删除函数
	bool Erase(const K& key)
	{
		KeyOfT kot;
		//用于遍历二叉树
		Node* parent = nullptr;
		Node* cur = _root;
		//用于标记实际的待删除结点及其父结点
		Node* delParentPos = nullptr;
		Node* delPos = nullptr;
		while (cur)
		{
			if (key < kot(cur->_data)) //所给key值小于当前结点的key值
			{
				//往该结点的左子树走
				parent = cur;
				cur = cur->_left;
			}
			else if (key > kot(cur->_data)) //所给key值大于当前结点的key值
			{
				//往该结点的右子树走
				parent = cur;
				cur = cur->_right;
			}
			else //找到了待删除结点
			{
				if (cur->_left == nullptr) //待删除结点的左子树为空
				{
					if (cur == _root) //待删除结点是根结点
					{
						_root = _root->_right; //让根结点的右子树作为新的根结点
						if (_root)
						{
							_root->_parent = nullptr;
							_root->_col = BLACK; //根结点为黑色
						}
						delete cur; //删除原根结点
						return true;
					}
					else
					{
						delParentPos = parent; //标记实际删除结点的父结点
						delPos = cur; //标记实际删除的结点
					}
					break; //进行红黑树的调整以及结点的实际删除
				}
				else if (cur->_right == nullptr) //待删除结点的右子树为空
				{
					if (cur == _root) //待删除结点是根结点
					{
						_root = _root->_left; //让根结点的左子树作为新的根结点
						if (_root)
						{
							_root->_parent = nullptr;
							_root->_col = BLACK; //根结点为黑色
						}
						delete cur; //删除原根结点
						return true;
					}
					else
					{
						delParentPos = parent; //标记实际删除结点的父结点
						delPos = cur; //标记实际删除的结点
					}
					break; //进行红黑树的调整以及结点的实际删除
				}
				else //待删除结点的左右子树均不为空
				{
					//替换法删除
					//寻找待删除结点右子树当中key值最小的结点作为实际删除结点
					Node* minParent = cur;
					Node* minRight = cur->_right;
					while (minRight->_left)
					{
						minParent = minRight;
						minRight = minRight->_left;
					}
					cur->_data = minRight->_data; //将待删除结点的_data改为minRight的_data
					delParentPos = minParent; //标记实际删除结点的父结点
					delPos = minRight; //标记实际删除的结点
					break; //进行红黑树的调整以及结点的实际删除
				}
			}
		}
		if (delPos == nullptr) //delPos没有被修改过,说明没有找到待删除结点
		{
			return false;
		}

		//记录待删除结点及其父结点(用于后续实际删除)
		Node* del = delPos;
		Node* delP = delParentPos;

		//调整红黑树
		if (delPos->_col == BLACK) //删除的是黑色结点
		{
			if (delPos->_left) //待删除结点有一个红色的左孩子(不可能是黑色)
			{
				delPos->_left->_col = BLACK; //将这个红色的左孩子变黑即可
			}
			else if (delPos->_right) //待删除结点有一个红色的右孩子(不可能是黑色)
			{
				delPos->_right->_col = BLACK; //将这个红色的右孩子变黑即可
			}
			else //待删除结点的左右均为空
			{
				while (delPos != _root) //可能一直调整到根结点
				{
					if (delPos == delParentPos->_left) //待删除结点是其父结点的左孩子
					{
						Node* brother = delParentPos->_right; //兄弟结点是其父结点的右孩子
						//情况一:brother为红色
						if (brother->_col == RED)
						{
							delParentPos->_col = RED;
							brother->_col = BLACK;
							RotateL(delParentPos);
							//需要继续处理
							brother = delParentPos->_right; //更新brother(否则在本循环中执行其他情况的代码会出错)
						}
						//情况二:brother为黑色,且其左右孩子都是黑色结点或为空
						if (((brother->_left == nullptr) || (brother->_left->_col == BLACK))
							&& ((brother->_right == nullptr) || (brother->_right->_col == BLACK)))
						{
							brother->_col = RED;
							if (delParentPos->_col == RED)
							{
								delParentPos->_col = BLACK;
								break;
							}
							//需要继续处理
							delPos = delParentPos;
							delParentPos = delPos->_parent;
						}
						else
						{
							//情况三:brother为黑色,且其左孩子是红色结点,右孩子是黑色结点或为空
							if ((brother->_right == nullptr) || (brother->_right->_col == BLACK))
							{
								brother->_left->_col = BLACK;
								brother->_col = RED;
								RotateR(brother);
								//需要继续处理
								brother = delParentPos->_right; //更新brother(否则执行下面情况四的代码会出错)
							}
							//情况四:brother为黑色,且其右孩子是红色结点
							brother->_col = delParentPos->_col;
							delParentPos->_col = BLACK;
							brother->_right->_col = BLACK;
							RotateL(delParentPos);
							break; //情况四执行完毕后调整一定结束
						}
					}
					else //delPos == delParentPos->_right //待删除结点是其父结点的左孩子
					{
						Node* brother = delParentPos->_left; //兄弟结点是其父结点的左孩子
						//情况一:brother为红色
						if (brother->_col == RED) //brother为红色
						{
							delParentPos->_col = RED;
							brother->_col = BLACK;
							RotateR(delParentPos);
							//需要继续处理
							brother = delParentPos->_left; //更新brother(否则在本循环中执行其他情况的代码会出错)
						}
						//情况二:brother为黑色,且其左右孩子都是黑色结点或为空
						if (((brother->_left == nullptr) || (brother->_left->_col == BLACK))
							&& ((brother->_right == nullptr) || (brother->_right->_col == BLACK)))
						{
							brother->_col = RED;
							if (delParentPos->_col == RED)
							{
								delParentPos->_col = BLACK;
								break;
							}
							//需要继续处理
							delPos = delParentPos;
							delParentPos = delPos->_parent;
						}
						else
						{
							//情况三:brother为黑色,且其右孩子是红色结点,左孩子是黑色结点或为空
							if ((brother->_left == nullptr) || (brother->_left->_col == BLACK))
							{
								brother->_right->_col = BLACK;
								brother->_col = RED;
								RotateL(brother);
								//需要继续处理
								brother = delParentPos->_left; //更新brother(否则执行下面情况四的代码会出错)
							}
							//情况四:brother为黑色,且其左孩子是红色结点
							brother->_col = delParentPos->_col;
							delParentPos->_col = BLACK;
							brother->_left->_col = BLACK;
							RotateR(delParentPos);
							break; //情况四执行完毕后调整一定结束
						}
					}
				}
			}
		}
		//进行实际删除
		if (del->_left == nullptr) //实际删除结点的左子树为空
		{
			if (del == delP->_left) //实际删除结点是其父结点的左孩子
			{
				delP->_left = del->_right;
				if (del->_right)
					del->_right->_parent = delP;
			}
			else //实际删除结点是其父结点的右孩子
			{
				delP->_right = del->_right;
				if (del->_right)
					del->_right->_parent = delP;
			}
		}
		else //实际删除结点的右子树为空
		{
			if (del == delP->_left) //实际删除结点是其父结点的左孩子
			{
				delP->_left = del->_left;
				if (del->_left)
					del->_left->_parent = delP;
			}
			else //实际删除结点是其父结点的右孩子
			{
				delP->_right = del->_left;
				if (del->_left)
					del->_left->_parent = delP;
			}
		}
		delete del; //实际删除结点
		return true;
	}

private:
	//拷贝树
	Node* _Copy(Node* root, Node* parent)
	{
		if (root == nullptr)
		{
			return nullptr;
		}
		Node* copyNode = new Node(root->_data);
		copyNode->_parent = parent;
		copyNode->_left = _Copy(root->_left, copyNode);
		copyNode->_right = _Copy(root->_right, copyNode);
		return copyNode;
	}

	//析构函数子函数
	void _Destroy(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}

	//左单旋
	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);
	}

	Node* _root; //红黑树的根结点
};

正向迭代器

//正向迭代器
template<class T, class Ref, class Ptr>
struct __TreeIterator
{
	typedef Ref reference; //结点指针的引用
	typedef Ptr pointer; //结点指针

	typedef RBTreeNode<T> Node; //结点的类型
	typedef __TreeIterator<T, Ref, Ptr> Self; //正向迭代器的类型

	Node* _node; //正向迭代器所封装结点的指针

	//构造函数
	__TreeIterator(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* cur = _node;
			Node* parent = cur->_parent;
			while (parent&&cur == parent->_right)
			{
				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* cur = _node;
			Node* parent = cur->_parent;
			while (parent&&cur == parent->_left)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent; //--后变为该结点
		}
		return *this;
	}
};

反向迭代器

//反向迭代器---迭代器适配器
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; //调用正向迭代器的operator!=
	}
	bool operator==(const Self& s) const
	{
		return _it == s._it; //调用正向迭代器的operator==
	}
};

set


	template<class K>
	class set
	{
		//仿函数
		struct SetKeyOfT
		{
			const K& operator()(const K& key) //返回键值Key
			{
				return key;
			}
		};
	public:
		typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator; //正向迭代器
		typedef typename RBTree<K, K, SetKeyOfT>::reverse_iterator reverse_iterator; //反向迭代器

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

		reverse_iterator rbegin()
		{
			return _t.rbegin();
		}
		reverse_iterator rend()
		{
			return _t.rend();
		}

		//插入函数
		pair<iterator, bool> insert(const K& key)
		{
			return _t.Insert(key);
		}
		//删除函数
		void erase(const K& key)
		{
			_t.Erase(key);
		}
		//查找函数
		iterator find(const K& key)
		{
			return _t.Find(key);
		}
	private:
		RBTree<K, K, SetKeyOfT> _t;
	};


map


	template<class K, class V>
	class map
	{
		//仿函数
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv) //返回键值对当中的键值Key
			{
				return kv.first;
			}
		};
	public:
		typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator; //正向迭代器
		typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::reverse_iterator reverse_iterator; //反向迭代器
		
		iterator begin()
		{
			return _t.begin();
		}
		iterator end()
		{
			return _t.end();
		}
		
		reverse_iterator rbegin()
		{
			return _t.rbegin();
		}
		reverse_iterator rend()
		{
			return _t.rend();
		}

		//插入函数
		pair<iterator, bool> insert(const pair<const K, V>& kv)
		{
			return _t.Insert(kv);
		}
		//[]运算符重载函数
		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = insert(make_pair(key, V()));
			iterator it = ret.first;
			return it->second;
		}
		//删除函数
		void erase(const K& key)
		{
			_t.Erase(key);
		}
		//查找函数
		iterator find(const K& key)
		{
			return _t.Find(key);
		}
	private:
		RBTree<K, pair<K, V>, MapKeyOfT> _t;
	};

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

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

相关文章

react基础Day04-React原理揭秘React路由基础

React原理揭秘 目标 能够说出React组件的更新机制能够对组件进行性能优化能够说出虚拟DOM和DIff算法 组件更新机制 setState() 的两个作用 修改state更新组件 过程&#xff1a;父组件重新渲染时&#xff0c;也会重新渲染子组件&#xff0c;但只会渲染当前组件子树&#xff…

[Android Studio]查看和修改Android API SDK的配置

&#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea; Android Debug&#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea; Topic 发布安卓学习过程中遇到问题解决过程&#xff0c;希望我的解决方案可以对小伙伴们有帮助。 &#x1f4cb;笔记目…

高并发系统设计 -- 缓存与数据库一致性问题详细讲解

CAP理论 首先来谈一下数据的一致性这个话题&#xff0c;所谓的一致性就是数据保持一致&#xff0c;在分布式系统中&#xff0c;可以理解为多个节点中的数据的值是一致的。 强一致性&#xff1a;这种一致性级别是最符合用户直觉的&#xff0c;它要求系统写入什么&#xff0c;读…

steam搬砖项目,信息差赚钱,内含全部讲解

Steam平台就是一个全球的游戏平台&#xff0c;搬砖主要是搬的一款火遍全球的游戏CSGO的装备和饰品。CS听说过吧&#xff0c;这款游戏就是CS的一个系列。&#xff08;通俗易懂的理解就是&#xff0c;从国外steam游戏平台购买装备&#xff0c;再挂到国内网易buff平台上进行售卖。…

【TypeScript】TS进阶-装饰器(九)

&#x1f431;个人主页&#xff1a;不叫猫先生 &#x1f64b;‍♂️作者简介&#xff1a;前端领域新星创作者、阿里云专家博主&#xff0c;专注于前端各领域技术&#xff0c;共同学习共同进步&#xff0c;一起加油呀&#xff01; &#x1f4ab;系列专栏&#xff1a;vue3从入门到…

ADI demo PL工程的编译-以adrv9371x_zc706为例子之使用Cygwin

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 ADI demo PL工程的编译-以adrv9371x_zc706为例子之使用Cygwin前言Cygwin 安装工程编译总结前言 之前一篇ADI demo工程的编译是ADI不推荐的方法&#xff0c;而且确实在后面NO…

【Django项目开发】django的信号机制(八)

文章目录1、什么是信号2、代码实现3、要想上面定义的信号机制生效,需要在apps.py中加载信号机制1、什么是信号 通俗来说,信号就是通信双方约定的一种信息通知方式,双方通过信号来确定发生了什么事情,然后决定自己应该做什么。 Django 中的信号用于在框架执行操作时解耦。当某…

2.1总线概述

文章目录一、总线简图二、概念与分类1.总线定义2.总线特点&#xff08;1&#xff09;分时&#xff08;2&#xff09;共享3.总线特性4.总线的分类&#xff08;1&#xff09;按数据传输格式①串行总线②并行总线&#xff08;2&#xff09;按总线功能①片内总线②系统总线③通信总…

【原创】linux实时应用如何printf输出不影响实时性?

linux实时应用printf输出 文章目录linux实时应用printf输出1. 前言2. linux终端输出3. 常见的NRT IO输出方案3.1 一种实现方式3.3 改进3. Xenomai3 printf()接口3.1 应用运行前环境初始化1. GCC特定语法2. libcobalt printf初始化流程3.2 libcobalt printf内存管理1. print_buf…

我的周刊(第074期)

我的信息周刊&#xff0c;记录这周我看到的有价值的信息&#xff0c;主要针对计算机领域&#xff0c;内容主题极大程度被我个人喜好主导。这个项目核心目的在于记录让自己有印象的信息做一个留存以及共享。&#x1f3af; 项目code-server[1]这是一款开源的基于 VSCode 的在线编…

【Linux系统】第四篇:Linux中编辑器vim的使用

文章目录一、vim的介绍1、vim的基本模式2、vim的使用二、命令模式1、光标移动操作2、文本复制、粘贴、剪切、撤销操作3、文本编辑相关操作三、插入模式四、底行模式底行模式命令集五、vim的配置原理六、sudo无法提权问题一、vim的介绍 vim是Linux下的一款多模式编辑器。 注意…

【已解决】右键以某应用打开xx文件时,没有“始终”选项怎么办

问题解决方案简单来说详细操作解释问题 右键以某应用打开xx文件时&#xff0c;没有“始终”选项 解决方案 简单来说 在注册表&#xff1a;计算机\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\找到要打开的文件后缀名&#xff0c;删除…

2022尚硅谷SSM框架跟学(五)Spring基础二

2022尚硅谷SSM框架跟学 五Spring基础二3.AOP3.1场景模拟3.1.1声明接口3.1.2创建实现类3.1.3创建带日志功能的实现类3.1.4提出问题(1)现有代码缺陷(2)解决思路(3)困难3.2代理模式3.2.1概念(1)介绍(2)生活中的代理(3)相关术语3.2.2静态代理3.2.3动态代理3.2.4测试3.3AOP概念及相关…

视频 | 生信 linux 实战题目讲解03

点击阅读原文跳转完整教案。1 Linux初探&#xff0c;打开新世界的大门1.1 Linux系统简介和目录理解1.1.1 为什么要用Linux系统1.1.2 Linux系统无处不在1.1.3 免费的Linux系统来一套1.1.4 Linux系统登录-联系远方的她1.1.5 初识Linux系统 - 黑夜中的闪烁是你的落脚点1.1.6 我的电…

使用混沌和非线性控制参数来提高哈里斯鹰优化算法的优化性能,解决车联网相关的路由问题(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

华为路由汇总实验

目录 OSPF路由聚合实验 在ABR上做路由聚合 在ASBR上做路由聚合 BGP路由聚合实验 在AR4-1上做静态聚合 在AR6-3上做手动聚合 ISIS路由聚合实验 R1配置路由聚合 OSPF路由聚合实验 OSPF——基本概念5&#xff08;汇总、更新、认证&#xff09;_静下心来敲木鱼的博客-CSDN博…

华为MPLS单域实验配置

目录 配置AS内的MPLS LDP协议 配置PE-PE之间的MP-BGP协议 在PE上配置VPN实例 在CE侧配置PE-CE的路由协议 在PE侧配置PE-CE的路由协议 在PE侧将CE的路由重发布进MP-BGP中 在CE侧将MP-BGP的路由重发布进CE中 MPLS隧道——单域基础理论讲解_静下心来敲木鱼的博客-CSDN博客h…

SPSS常用的10种统计分析

目录 实验一 地理数据的统计处理 一、实验目的 二、实验内容 三、实验步骤 实验二 双变量相关分析 一、实验目的 二、实验内容 三、实验步骤 实验三 主成分分析 一、实验目的 二、实验内容 三、实验步骤 实验四 因子分析 一、实验目的 二、实验内容 三、实…

【Linux】缓冲区 进度条小程序

目录 一、\r && \n 二、缓冲区的概念 三、小程序编写 1、倒数小程序 2、进度条小程序 一、\r && \n C语言中有很多字符&#xff0c;但是宏观上可以分成两类&#xff1a;可显字符、控制字符。 可显字符包括我们见到的 1、2、3....&#xff0c;a、b、c....…

历史最全事件抽取任务分类、经典论文、模型及数据集整理分享

事件抽取技术是从非结构化信息中抽取出用户感兴趣的事件&#xff0c;并以结构化呈现给用户。事件抽取任务可分解为4个子任务: 触发词识别、事件类型分类、论元识别和角色分类任务。其中&#xff0c;触发词识别和事件类型分类可合并成事件识别任务。事件识别判断句子中的每个单词…