C++:使用红黑树封装map和set

news2025/1/1 22:21:41

目录

一. 如何使用一颗红黑树同时实现map和set

二. 红黑树的节点插入操作

三. 红黑树迭代器的实现

3.1 begin()和end()

3.2 operator++和operator--

3.3 红黑树迭代器实现完整版代码

四. map和set的封装

附录:用红黑树封装map和set完整版代码

1. RBTree.h文件

2. map.h文件

3. set.h文件


一. 如何使用一颗红黑树同时实现map和set

我们知道,map和set的底层都是通过红黑树来实现的,它们的不同在于:map存储的是一键值对,键值对的第一个数据用于搜索树的比较,第二个数据用于与之配对,而set则只有一个数据。需要采用模板(泛型编程)的方法来定义红黑树节点,并在map和set中给定红黑树类模板不同的模板参数类型,

图1.1 红黑树节点、红黑树类模板与map和set类模板之间的关系

观察图1.1,我们可以总结出RBTreeNode、RBTree、map和set类模板之间的如下规则:

  • 红黑树节点只有一个模板参数,set有一个模板参数,其本身就是节点的数据类型,map有两个模板参数,分别为创建键值对的first和second数据类型。
  • 在map中,RBTreeNode中的模板参数类型为pair键值对,由于map中要取出键值对的first比较创建搜索树,而直接用>或<对pair比较不符合要求,因此定义仿函数KeyOfV来获取用于比较的数据。

二. 红黑树的节点插入操作

用于对map和set封装的红黑树的查找操作与普通红黑树一致,唯一的不同在于需要创建KeyOfV类的对象,并使用仿函数进行比较。如果希望与库中的insert更加贴合,则应返还键值对pair<iterator, bool>类型数据。

具体的红黑树的插入实现流程,可参考博文:C++数据结构:手撕红黑树_【Shine】光芒的博客-CSDN博客

代码2.1:(红黑树节点插入)

	std::pair<iterator, bool> insert(const T& date)
	{
		//插入第一个节点
		if (_root == nullptr)
		{
			_root = new Node(date);
			_root->_col = BLACK;   //根节点为黑色
			return std::make_pair(_root, true);
		}

		KeyOfT kov;   //用于筛选比较数据的类对象

		//寻找节点插入的位置
		Node* parent = nullptr;   
		Node* cur = _root;

		while (cur)
		{
			//如果cur节点的key值大于插入键值对的key,向左子树查找
			if (kov(cur->_date) > kov(date))
			{
				parent = cur;
				cur = cur->_left;
			}
			else if(kov(cur->_date) < kov(date))  //如果cur节点的key值小于插入键值对的key,向左子树查找
			{
				parent = cur;
				cur = cur->_right;
			}
			else  //相等表明节点已存在,插入失败
			{
				return std::make_pair(cur, false);
			}
		}

		//判断新节点是parent的左节点还是右节点,链接
		//默认新插入的节点为红色
		cur = new Node(date);
		Node* newNode = cur;
		cur->_col = RED;
		cur->_parent = parent;

		if (kov(parent->_date) > kov(date))
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}

		//如果parent节点不为空且为红色,那么红黑树的结构在插入节点后被破坏,需要调整
		while (parent && parent->_col == RED)
		{
			Node* grandParent = parent->_parent;   //祖父节点
			assert(grandParent);
			assert(grandParent->_col == BLACK);   //断言检查,如果祖父节点为空或为黑色,那么红黑树结构在节点插入之前就存在问题

			if (parent == grandParent->_left)  //插入在祖父节点的左子树
			{
				Node* uncle = grandParent->_right;

				//情况一:cur为红,parent为红,grandFather为黑,uncle为红
				if (uncle && uncle->_col == RED)
				{
					//将parent节点和uncle节点变为黑,grandFather节点变为红,然后继续向上调整
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandParent->_col = RED;

					cur = grandParent;
					parent = cur->_parent;
				}	
				else  //情况二、三:cur为红,parent为红,grandFather为黑,uncle不存在或为黑
				{
					if (parent->_left == cur)
					{
						//情况二 -- 进行右单旋 + 变色(parent变黑,grandFather变红)
						//    g
						//  p   u
						//c
						RotateR(grandParent);
						parent->_col = BLACK;
						grandParent->_col = RED;
					}
					else
					{
						//情况三 -- 进行左右双旋 + 变色(cur节点变为黑,grandFater节点变为红)
						//    g
						//  p   u
						//   u 
						RotateLR(grandParent);
						cur->_col = BLACK;
						grandParent->_col = RED;
					}

					break;
				}
			}
			else  //parent == grandParent->_right
			{
				Node* uncle = grandParent->_left;  //叔叔节点

				//情况一:cur为红,parent为红,grandFather为黑,uncle为红
				if (uncle && uncle->_col == RED)
				{
					//将parent节点和uncle节点变为黑,grandFather节点变为红,然后继续向上调整
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandParent->_col = RED;

					cur = grandParent;
					parent = cur->_parent;
				}
				else
				{
					//情况二、三:cur为红,parent为红,grandFather为黑,uncle不存在或为黑
					if (parent->_right == cur)
					{
						//情况二 -- 进行右单旋 + 变色(parent变黑,grandFather变红)
						//   g
						// u   p
						//       c
						RotateL(grandParent);
						parent->_col = BLACK;
						grandParent->_col = RED;
					}
					else
					{
						//情况三 -- 进行右左双旋 + 变色(cur节点变为黑,grandFater节点变为红)
						//    g
						// u     p
						//     c
						RotateRL(grandParent);
						cur->_col = BLACK;
						grandParent->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;   //根节点为黑色

		return std::make_pair(newNode, true);
	}

三. 红黑树迭代器的实现

我们要额外封装一个类struct  __RBTree_iterator_来实现红黑树迭代器,这个类有三个模板参数T、Ref、Ptr,这样做的目的是定义一份迭代器类模板就可以实现普通迭代器和const迭代器。

  • typedef __RBTree_iterator_<T, T&, T*> iterator;   //红黑树迭代器

3.1 begin()和end()

STL标准规定迭代器区间begin()和end()为左闭右开区间,而对红黑树遍历获取的数据为升序序列,因此,begin()应该为左下角位置处的节点,end()应该为哨兵卫的头结点_head。这里从便于理解和实现的角度出发,将_head设为nullptr,即:end()返回空指针。

图3.1 红黑树的begin()和end()

代码3.1:(begin和end)

	//获取begin()位置 -- 最左侧节点
	iterator begin()
	{
		Node* left = _root;
		while (left && left->_left)
		{
			left = left->_left;
		}

		return iterator(left);
	}

	iterator end()
	{
		return iterator(nullptr);
	}

3.2 operator++和operator--

operator++就是查找中序遍历的下一个节点,可分为两种情况讨论:

  • 如果节点的右子树不为空,则为右子树最左侧的节点。
  • 如果节点的右子树为空,则向上查找孩子不是父亲的右子节点的那个节点。
图3.2 operator++的两种情况

operator--与operator++正好相反,为找中序遍历的前一个节点,亦可分两种情况讨论:

  • 如果节点的左子树不为空,则为左子树最右侧的节点。
  • 如果节点的左子树为空,则向上查找孩子不是父亲的左子节点的那个节点。
图3.3 operator--的两种情况

代码3.2:(operator++和operator--)

	typedef RBTreeNode<T> Node;    //红黑树节点
	typedef __RBTree_iterator_<T, Ref, Ptr> Self;	
    
    //++运算符重载函数
	Self& operator++()
	{
		if (_node->_right != nullptr)
		{
			//找右子树的最左侧节点
			Node* left = _node->_right;
			while (left->_left)
			{
				left = left->_left;
			}

			_node = left;
		}
		else
		{
			//找孩子节点为左孩子节点的位置,或者父亲节点为空
			Node* parent = _node->_parent;
			Node* cur = _node;

			while (parent && parent->_right == cur)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

	//--运算符重载函数
	Self& operator--()
	{
		if (_node->_left != nullptr)
		{
			Node* right = _node->_left;
			while (right->_right)
			{
				right = right->_right;
			}

			_node = right;
		}
		else
		{
			Node* parent = _node->_parent;
			Node* cur = _node;

			while (parent && parent->_left == cur)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

3.3 红黑树迭代器实现完整版代码

//红黑树迭代器模板
template<class T, class Ref, class Ptr>
struct __RBTree_iterator_
{
	typedef RBTreeNode<T> Node;    //红黑树节点
	typedef __RBTree_iterator_<T, Ref, Ptr> Self;

	Node* _node;

	//构造函数
	__RBTree_iterator_(Node* node)
		: _node(node)
	{ }

	//解引用函数
	Ref operator*()
	{
		return _node->_date;
	}

	//成员访问操作符->重载
	Ptr operator->()
	{
		return &_node->_date;
	}

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

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

	//++运算符重载函数
	Self& operator++()
	{
		if (_node->_right != nullptr)
		{
			//找右子树的最左侧节点
			Node* left = _node->_right;
			while (left->_left)
			{
				left = left->_left;
			}

			_node = left;
		}
		else
		{
			//找孩子节点为左孩子节点的位置,或者父亲节点为空
			Node* parent = _node->_parent;
			Node* cur = _node;

			while (parent && parent->_right == cur)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

	//--运算符重载函数
	Self& operator--()
	{
		if (_node->_left != nullptr)
		{
			Node* right = _node->_left;
			while (right->_right)
			{
				right = right->_right;
			}

			_node = right;
		}
		else
		{
			Node* parent = _node->_parent;
			Node* cur = _node;

			while (parent && parent->_left == cur)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}
};

四. map和set的封装

map和set底层都是通过红黑树来实现的,只需在map和set中定义一颗红黑树的自定义类型变量,然后调用红黑树的接口函数即可。

这里需要特别注意的是map中的operator[]函数,其实现为先调用insert函数,insert函数返回一个键值对,first为插入的节点或Key与插入节点相等位置的迭代器,second为bool类型变量,用来表示是否有新节点成功插入。函数只需返回insert返回的键值对的second的引用即可。

注意用于提取Key的仿函数要在map和set中分别定义。

代码4.1:(map的封装)

namespace zhang
{
	template <class K, class V>
	class map
	{
		struct KeyOfV
		{
			const K& operator()(const std::pair<K,V>& val)
			{
				return val.first;
			}
		};

	public:
		typedef typename RBTree<K, std::pair<K, V>, KeyOfV>::iterator iterator;

		std::pair<iterator, bool> insert(const std::pair<K, V>& kv)
		{
			return _t.insert(kv);
		}

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

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

		V& operator[](const K& key)
		{
			std::pair<iterator, bool> ret = insert(std::make_pair(key, V()));
			return ret.first->second;
		}

	private:
		RBTree<K, std::pair<K,V>, KeyOfV> _t;   //红黑树
	};
}

代码4.2:(set的模拟实现)

namespace zhang
{
	template <class K>
	class set
	{
		struct KeyOfV
		{
			const K& operator()(const K& val)
			{
				return val;
			}
		};

	public:
		typedef typename RBTree<K, K, KeyOfV>::iterator iterator;

		std::pair<iterator, bool> insert(const K& key)
		{
			return _t.insert(key);
		}

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

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

		K& operator[](const K& key)
		{
			std::pair<iterator, bool> ret = insert(key);
			return *ret.first;
		}

	private:
		RBTree<K, K, KeyOfV> _t;   //红黑树
	};
}

附录:用红黑树封装map和set完整版代码

1. RBTree.h文件

#include<iostream>
#include<assert.h>

//枚举常量 -- 红色、黑色
enum Color
{
	RED,
	BLACK
};

//定义红黑树节点
template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;

	T _date;  //数据值(set为单个值,map为键值对pair)
	Color _col;   //节点颜色

	RBTreeNode(const T& date)   //节点构造函数
		: _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _date(date)
		, _col(RED)
	{ }
};

//红黑树迭代器模板
template<class T, class Ref, class Ptr>
struct __RBTree_iterator_
{
	typedef RBTreeNode<T> Node;    //红黑树节点
	typedef __RBTree_iterator_<T, Ref, Ptr> Self;

	Node* _node;

	//构造函数
	__RBTree_iterator_(Node* node)
		: _node(node)
	{ }

	//解引用函数
	Ref operator*()
	{
		return _node->_date;
	}

	//成员访问操作符->重载
	Ptr operator->()
	{
		return &_node->_date;
	}

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

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

	//++运算符重载函数
	Self& operator++()
	{
		if (_node->_right != nullptr)
		{
			//找右子树的最左侧节点
			Node* left = _node->_right;
			while (left->_left)
			{
				left = left->_left;
			}

			_node = left;
		}
		else
		{
			//找孩子节点为左孩子节点的位置,或者父亲节点为空
			Node* parent = _node->_parent;
			Node* cur = _node;

			while (parent && parent->_right == cur)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

	//--运算符重载函数
	Self& operator--()
	{
		if (_node->_left != nullptr)
		{
			Node* right = _node->_left;
			while (right->_right)
			{
				right = right->_right;
			}

			_node = right;
		}
		else
		{
			Node* parent = _node->_parent;
			Node* cur = _node;

			while (parent && parent->_left == cur)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}
};


//红黑树类模板
template<class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node;    //类型重定义红黑树节点

public:
	typedef __RBTree_iterator_<T, T&, T*> iterator;   //迭代器

	std::pair<iterator, bool> insert(const T& date)
	{
		//插入第一个节点
		if (_root == nullptr)
		{
			_root = new Node(date);
			_root->_col = BLACK;   //根节点为黑色
			return std::make_pair(_root, true);
		}

		KeyOfT kov;   //用于筛选比较数据的类对象

		//寻找节点插入的位置
		Node* parent = nullptr;   
		Node* cur = _root;

		while (cur)
		{
			//如果cur节点的key值大于插入键值对的key,向左子树查找
			if (kov(cur->_date) > kov(date))
			{
				parent = cur;
				cur = cur->_left;
			}
			else if(kov(cur->_date) < kov(date))  //如果cur节点的key值小于插入键值对的key,向左子树查找
			{
				parent = cur;
				cur = cur->_right;
			}
			else  //相等表明节点已存在,插入失败
			{
				return std::make_pair(cur, false);
			}
		}

		//判断新节点是parent的左节点还是右节点,链接
		//默认新插入的节点为红色
		cur = new Node(date);
		Node* newNode = cur;
		cur->_col = RED;
		cur->_parent = parent;

		if (kov(parent->_date) > kov(date))
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}

		//如果parent节点不为空且为红色,那么红黑树的结构在插入节点后被破坏,需要调整
		while (parent && parent->_col == RED)
		{
			Node* grandParent = parent->_parent;   //祖父节点
			assert(grandParent);
			assert(grandParent->_col == BLACK);   //断言检查,如果祖父节点为空或为黑色,那么红黑树结构在节点插入之前就存在问题

			if (parent == grandParent->_left)  //插入在祖父节点的左子树
			{
				Node* uncle = grandParent->_right;

				//情况一:cur为红,parent为红,grandFather为黑,uncle为红
				if (uncle && uncle->_col == RED)
				{
					//将parent节点和uncle节点变为黑,grandFather节点变为红,然后继续向上调整
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandParent->_col = RED;

					cur = grandParent;
					parent = cur->_parent;
				}	
				else  //情况二、三:cur为红,parent为红,grandFather为黑,uncle不存在或为黑
				{
					if (parent->_left == cur)
					{
						//情况二 -- 进行右单旋 + 变色(parent变黑,grandFather变红)
						//    g
						//  p   u
						//c
						RotateR(grandParent);
						parent->_col = BLACK;
						grandParent->_col = RED;
					}
					else
					{
						//情况三 -- 进行左右双旋 + 变色(cur节点变为黑,grandFater节点变为红)
						//    g
						//  p   u
						//   u 
						RotateLR(grandParent);
						cur->_col = BLACK;
						grandParent->_col = RED;
					}

					break;
				}
			}
			else  //parent == grandParent->_right
			{
				Node* uncle = grandParent->_left;  //叔叔节点

				//情况一:cur为红,parent为红,grandFather为黑,uncle为红
				if (uncle && uncle->_col == RED)
				{
					//将parent节点和uncle节点变为黑,grandFather节点变为红,然后继续向上调整
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandParent->_col = RED;

					cur = grandParent;
					parent = cur->_parent;
				}
				else
				{
					//情况二、三:cur为红,parent为红,grandFather为黑,uncle不存在或为黑
					if (parent->_right == cur)
					{
						//情况二 -- 进行右单旋 + 变色(parent变黑,grandFather变红)
						//   g
						// u   p
						//       c
						RotateL(grandParent);
						parent->_col = BLACK;
						grandParent->_col = RED;
					}
					else
					{
						//情况三 -- 进行右左双旋 + 变色(cur节点变为黑,grandFater节点变为红)
						//    g
						// u     p
						//     c
						RotateRL(grandParent);
						cur->_col = BLACK;
						grandParent->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;   //根节点为黑色

		return std::make_pair(newNode, true);
	}

	//中序遍历函数
	void InOrder()
	{
		_InOrder(_root);
		std::cout << std::endl;
	}

	//红黑树检验函数
	bool IsRBTree()
	{
		//空树是合法的红黑树
		if (_root == nullptr)
		{
			return true;
		}

		//检查根节点颜色
		if (_root->_col == RED)
		{
			std::cout << "根节点颜色不是黑色" << std::endl;
		}

		int baseBlackNum = 0;   //基准黑色节点个数
		//以最左侧路径为基准,计算黑色节点个数,每条路径黑色节点数目都应该相同
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				++baseBlackNum;
			}

			cur = cur->_left;
		}

		bool blackNumTrue = PrevCheck(_root, 0, baseBlackNum);   //检查每条路径黑色节点数目是否相同
		bool colorTrue = CheckColor(_root);  //检查是否存在连续红色节点

		return blackNumTrue && colorTrue;
	}

	//获取begin()位置 -- 最左侧节点
	iterator begin()
	{
		Node* left = _root;
		while (left && left->_left)
		{
			left = left->_left;
		}

		return iterator(left);
	}

	iterator end()
	{
		return iterator(nullptr);
	}

private:
	bool CheckColor(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}

		//如果本节点为红色且父亲节点也为红色,证明存在连续红色节点,结构错误
		if (root->_col == RED && root->_parent && root->_parent->_col == RED)
		{
			std::cout << "存在连续的红色节点" << std::endl;
			return false;
		}

		return CheckColor(root->_left) && CheckColor(root->_right);
	}

	bool PrevCheck(Node* root, int blackNum, int baseBlackNum)
	{
		if (root == nullptr)
		{
			if (blackNum != baseBlackNum)
			{
				std::cout << "每条路径上黑色节点的数目不同" << std::endl;
				return false;
			}
			else
			{
				return true;
			}
		}

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

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

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

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

	void RotateR(Node* parent)   //右单旋函数
	{
		Node* pNode = parent->_parent;    
		Node* pL = parent->_left;   //左子节点
		Node* pLR = pL->_right;   //左子节点的右子节点

		//将pLR节点托管给parent节点的左子节点
		parent->_left = pLR;
		if (pLR != nullptr)
		{
			pLR->_parent = parent;
		}

		//将父亲节点托管给pL节点的右子节点
		pL->_right = parent;  
		parent->_parent = pL;

		//此时这颗进行旋转的子树的根节点变为了pL,pL要与pNode节点连接
		if (parent == _root)
		{
			_root = pL;
			pL->_parent = nullptr;
		}
		else
		{
			pL->_parent = pNode;

			if (pNode->_left == parent)
			{
				pNode->_left = pL;
			}
			else
			{
				pNode->_right = pL;
			}
		}
	}

	void RotateL(Node* parent)   //左单旋函数
	{
		Node* pNode = parent->_parent;
		Node* pR = parent->_right;    //右子节点
		Node* pRL = pR->_left;   //右子节点的左子节点

		//将pLR节点托管给parent节点的右子节点
		parent->_right = pRL;
		if (pRL != nullptr)
		{
			pRL->_parent = parent;
		}

		//将parent节点托管给pR的左子节点
		pR->_left = parent;
		parent->_parent = pR;

		if (_root == parent)
		{
			_root = pR;
			_root->_parent = nullptr;
		}
		else
		{
			pR->_parent = pNode;

			if (pNode->_left == parent)
			{
				pNode->_left = pR;
			}
			else
			{
				pNode->_right = pR;
			}
		}
	}

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

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

private:
	Node* _root = nullptr;
};

2. map.h文件

#include "RBTree.h"

namespace zhang
{
	template <class K, class V>
	class map
	{
		struct KeyOfV
		{
			const K& operator()(const std::pair<K,V>& val)
			{
				return val.first;
			}
		};

	public:
		typedef typename RBTree<K, std::pair<K, V>, KeyOfV>::iterator iterator;

		std::pair<iterator, bool> insert(const std::pair<K, V>& kv)
		{
			return _t.insert(kv);
		}

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

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

		V& operator[](const K& key)
		{
			std::pair<iterator, bool> ret = insert(std::make_pair(key, V()));
			return ret.first->second;
		}

	private:
		RBTree<K, std::pair<K,V>, KeyOfV> _t;   //红黑树
	};
}

3. set.h文件

namespace zhang
{
	template <class K>
	class set
	{
		struct KeyOfV
		{
			const K& operator()(const K& val)
			{
				return val;
			}
		};

	public:
		typedef typename RBTree<K, K, KeyOfV>::iterator iterator;

		std::pair<iterator, bool> insert(const K& key)
		{
			return _t.insert(key);
		}

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

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

		K& operator[](const K& key)
		{
			std::pair<iterator, bool> ret = insert(key);
			return *ret.first;
		}

	private:
		RBTree<K, K, KeyOfV> _t;   //红黑树
	};
}

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

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

相关文章

表情识别 emotion recognition

facial expression recognition 表情&#xff0c;人脸上的肌肉状态&#xff0c;可以表达人类的情绪。 1970年&#xff0c;Ekman定义了六种基本表情 如何定义&#xff1f; 动作单元(action unit)是定义表情的重要工具。 心理学家和生物学家认为&#xff0c;人的表情可以分解…

【HCIP】三层架构综合实验

目录 需求&#xff1a; 一、拓扑设计 二、配置 ①eth-trunk ②创建vlan & 划分vlan & trunk干道配置 ③STP生成树根节点备份&负责分担 ④SVI及VRRP网关冗余设置 ⑤DHCP ⑥通公网 ⑦验证 三层架构&#xff1a;核心层&#xff0c;汇聚层&#xff0c;接入层 …

QT桌面项目(第一个应用程序 桌面壁纸软件)

文章目录 前言一、壁纸切换程序的布局二、添加资源文件三、代码编写四、主界面代码五、程序的编写思路六、程序效果总结 前言 上节课我们已经做好了APP的按键图标了&#xff0c;这篇文章就让我们来开始制作第一个桌面程序吧。 一、壁纸切换程序的布局 这个是windows上浏览器…

vue3使用指南

vue3使用指南 主要介绍vue3的使用&#xff0c;同时会涉及到vue2&#xff0c;并会讲解其中的一些差异。 安装 CDN引入 如果想快速体验&#xff0c;可以直接通过cdn进行引入。 <div id"app">{{msg}}</div> <script src"https://unpkg.com/vue…

谷歌推出下一代大型语言模型 PaLM 2

谷歌在 2023 年度 I/O 大会上宣布推出了其下一代大型语言模型 PaLM 2&#xff0c;擅长高级推理任务&#xff0c;包括代码和数学、分类和问答、翻译和多语言能力以及自然语言生成。 谷歌声称 PaLM 2 是一种最先进的语言模型&#xff0c;要优于其之前所有的 LLM&#xff0c;包括…

网络拓扑架构规划设计

小型组网架构 1.网络拓扑 终端用户接入到交换机,交换机直连防火墙构成的简单网络,防火墙连接internet,对内网的用户进行安全控制 2.特点 用户接入数量较少:小型网络应用于接入用户数量较少的场景,一般支持几个至几十个用户 网络覆盖范围小:网络覆盖范围一般也是一个…

网络拓扑图制作软件

1.亿图图示(EdrawMax) 最像VISIO的网络拓扑制作工具 亿图图示实际上就是国产的Visio,与Visio非常相似。支持B/S架构,查看拓扑非常方便,模版也比较丰富。缺点与Visio也类似,画简单的网络拓扑没有问题,但针对大型网络拓扑管理而言,既需要能制作拓扑,又需要具有管理分析…

Hystrix详解及实践---SpringCloud组件(四)

Hystrix详解及实践 1.Hystrix简介2.雪崩问题3.服务降级、线程隔离、原理3.1.服务降级实践&#xff08;在feign的基础上实现&#xff09;1 添加Hystrix依赖2.在yml中开启熔断功能3.编写降级逻辑4.重启测试 4.服务熔断(Circuit Breaker)、原理4.1. 熔断原理4.2.动手实践 5.Hystri…

5个模板非常多的免费样机素材分享

样机素材是设计行业的不可缺少的素材之一&#xff0c;设计师可以将自己的设计作品&#xff0c;应用到一个效果图中进行展示&#xff0c;让你的设计作品看起来更加形象逼真。 本文分享5个非常给力的样机素材网站 1.即时设计资源社区 即时设计是一款「专业UI设计工具」&#x…

【edusrc】某校园网登录系统存在的一个简单爆破

前言 红中(hong_zh0) CSDN内容合伙人、2023年新星计划web安全方向导师、 华为MindSpore截至目前最年轻的优秀开发者、IK&N战队队长、 阿里云专家博主、华为网络安全云享专家、腾讯云自媒体分享计划博主 本文主讲思路&#xff0c;可能有点废话。 该漏洞已修复 最开始的思…

华为OD机试真题 Java 实现【字符串重新排序】【2023Q1 100分】

一、题目描述 给定一个字串s&#xff0c;s包含以空格分隔的若干个单词&#xff0c;请对s进行如下处理后输出&#xff1a; 1、单词内部调整 对每个单词字母重新按字典序排序。 2、单词间顺序调整: 统计每个单词出现的次数&#xff0c;并按次数降序排列&#xff1b;次数相同…

存下吧!Spring高频面试题总结

Spring是什么&#xff1f; Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。 Spring的优点 通过控制反转和依赖注入实现松耦合。支持面向切面的编程&#xff0c;并且把应用业务逻辑和系统服务分开。通过切面和模板减少样板式代码。声明式事务的支持。可以从单…

Windows系统下Chromedriver.exe安装及配置

Windows系统下Chromedriver.exe安装及配置 在利用selenium工具进行Web自动化测试时&#xff0c;必须先要安装浏览器驱动&#xff0c;通常比较常用的是谷歌浏览器和火狐浏览器。 一、浏览器驱动下载地址 1.浏览器驱动官网&#xff1a;http://chromedriver.storage.googleapis…

计算环境安全

计算环境安全 操作系统安全安全机制标识与鉴别访问控制权限管理信道保护安全审计内存保护与文件系统保护 安全部署原则操作系统安全配置密码远程暴力破解安全审计 针对系统的攻击信息收集公开信息收集-搜索引擎信息收集与分析的防范 缓冲区溢出缓冲区溢出基础-堆栈、指针、寄存…

Dubbo源码篇03---点点直连如何实现及背后原理

Dubbo源码篇03---从点点直连探究Complier编译的原理 什么是点点直连实际需求如何实现动态编译&#xff1f;如何发起调用?点点直连原理实现点点直连消费端提供端测试 点点直连小结 什么是点点直连 Dubbo正常的请求模型&#xff0c;都是消费端从注册中心拉取服务提供者列表&…

spring 命令执行 (CVE-2022-22947)

漏洞原理 该漏洞产生的原因是由于&#xff0c;当AddResponseHeaderGatewayFilterFactory传入的值进行计算(getValue())的时候&#xff0c;会逐一向上调用对应的方法&#xff0c;直到进入带有SpEL表达式解析器的位置进行最后的解析&#xff0c;从而触发了SpEL表达式注入漏洞。漏…

分组背包问题 java

&#x1f351; 算法题解专栏 &#x1f351; 分组背包问题 输入 3 5 2 1 2 2 4 1 3 4 1 4 5输出 8&#x1f468;‍&#x1f3eb; 参考题解 &#x1f351; 终极简化版 import java.util.Scanner;public class 分组背包极简版 {static int N 110;static int[] f new int[N]…

【大数据之Hadoop】三十、HDFS故障排除

使用3台服务器&#xff0c;恢复yarn快照。 1 NameNode故障处理 出现NameNode进程挂了并且存储的数据也丢失了&#xff0c;怎么恢复NameNode。 故障模拟&#xff1a; &#xff08;1&#xff09;kill掉NameNode的进程&#xff1a; kill -9 进程ID&#xff08;2&#xff09;删…

SpringCloud:微服务保护之授权规则

授权规则可以对请求方来源做判断和控制。 1.授权规则 1.1.基本规则 授权规则可以对调用方的来源做控制&#xff0c;有白名单和黑名单两种方式。 白名单&#xff1a;来源&#xff08;origin&#xff09;在白名单内的调用者允许访问 黑名单&#xff1a;来源&#xff08;origi…

【JAVAEE】阻塞队列的实现

目录 阻塞队列 生产者消费者模型 消息队列 消息队列的作用 1.解耦 2.削峰填谷 3.异步 演示JDK中的阻塞队列 实现一个阻塞队列 阻塞队列 队列&#xff0c;是一种先进先出&#xff08;FIFO&#xff09;数据结构。 阻塞队列也满足队列的特性&#xff1a; 入队元素时&am…