C++:map和set的封装

news2024/10/7 8:20:37

      关于红黑树的模拟实现,大家不清楚的先去看看博主的博客再来看这篇文章,因为set和map的封装底层都是利用用的红黑树。所以这里不会过多介绍红黑树的相关内容,而更多的是去为了契合STL中的红黑树去进行改造,让封装的set和map能够去复用我们的这份代码

DS进阶:AVL树和红黑树-CSDN博客

      在模拟实现之前,我们肯定要尝试去看看源码是如何实现的!我们会发现其实map和set的底层都是用的红黑树去封装的

      但是你可能会有这样的疑惑,map是kv模型,set是k模型,那难道stl底层封装了两颗红黑树么??其实并不是的,创建stl的大佬们为了增加代码的复用性,想方设法地想让map和set同时复用一颗红黑树。而解决方法就是通过控制模版参数来区分map和set。

     既然底层是套的红黑树的壳子,我们就要来研究库里面的红黑树究竟通过了什么方法来让map和set都能够复用这份代码。

一、STL中的红黑树

1.1 利用模版参数控制和区分map和set

我们先来看看stl中的红黑树的模版参数,然后进行分析

   接下来我们来看看第三个模版参数的作用究竟是什么

总结:

第1个模版参数是为了帮助我们拿到Key的类型,因为find、erase的接口都是Key类型比较方便

第2个模版参数决定了红黑树节点中存的是key还是pair,以此来区分map和set

第3个模版参数是通过仿函数决定了是拿什么去进行比较,对set来说就是拿key,对pair来说就是拿他的first。

第4个模版参数是具体的比较逻辑,比如说我们传的是指针,但是我们并不想通过指针比而是通过指针解引用的类型比,就可以通过传这个仿函数去控制其比较的行为。

第5个是stl实现的一个堆内存管理器,是为了提高从堆区申请内存的效率,基本上所有的stl容器都会涉及到这个,所以目前暂时不需要太在意!

1.2 stl中的红黑树结构

在该图中,设置了一个哨兵节点,哨兵节点的左指向最小节点5,最大节点的右指向哨兵节点header, 为什么要这样设计呢??

      STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,
可以得到一个有序的序列,
因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位
置,end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪块?
能否给成nullptr呢?答案是行不通的,因为对end()位置的迭代器进行--操作,必须要能找最
后一个元素,此处就不行,因此最好的方式是将end()放在头结点的位置:

 

       但是这样虽然方便我们找到第一个节点和最后一个节点,但是每一次都要最最左端和最右端的节点进行和头节点之间的联系,其实比较麻烦,所以下面我们直接改造成不带哨兵节点的红黑树。去模拟实现迭代器。

1.3 改造并模拟实现红黑树的迭代器

但是最最关键的逻辑就是,实现++和--这样迭代器才能跑的起来,下面我们来进行分析

迭代器的封装

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


	// 1、typedef __RBTreeIterator<T, T&, T*> itertaor;  拷贝构造
	// 2、 typedef __RBTreeIterator<T, const T&, const T*> const_itertaor;
	//  支持普通迭代器构造const迭代器的构造函数

	_RBTreeIterator(const _RBTreeIterator<T, T&, T*>& it) //隐私类型转化
		:_node(it._node)
	{}




	Ref operator*()
	{
		return _node->_data; //解引用拿到对应的东西  map拿到pair set拿到key
	}

	Ptr operator->() //返回对应的指针类型
	{
		return &operator*();
	}

	bool operator!=(const Self& s)
	{
		return _node != s._node;//判断两个迭代器是否相同
	}

	bool operator==(const Self& s)
	{
		return _node == s._node;//判断两个迭代器是否相同
	}

	Self& operator++()  //实现迭代器的++
	{
		if (_node->_right)
		{
		   //如有右不为空,那么就去找到  右子树的最左路节点
			Node* subright = _node->_right;
			while (subright->_left)  subright = subright->_left; //找到最左路节点
			_node = subright;
		}
		else
		{
		   //右为空,沿着到根的路径,找孩子是父亲左的那个祖先
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && parent->_right == cur)
			{
				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 && parent->_left == cur)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}
};

1.4 红黑树实现的全部代码 

enum Colour
{
	RED,
	BLACK,
};

template<class T> //T表示传的是K还是pair
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)
	{}
};

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


	// 1、typedef __RBTreeIterator<T, T&, T*> itertaor;  拷贝构造
	// 2、 typedef __RBTreeIterator<T, const T&, const T*> const_itertaor;
	//  支持普通迭代器构造const迭代器的构造函数

	_RBTreeIterator(const _RBTreeIterator<T, T&, T*>& it) //隐私类型转化
		:_node(it._node)
	{}




	Ref operator*()
	{
		return _node->_data; //解引用拿到对应的东西  map拿到pair set拿到key
	}

	Ptr operator->() //返回对应的指针类型
	{
		return &operator*();
	}

	bool operator!=(const Self& s)
	{
		return _node != s._node;//判断两个迭代器是否相同
	}

	bool operator==(const Self& s)
	{
		return _node == s._node;//判断两个迭代器是否相同
	}

	Self& operator++()  //实现迭代器的++
	{
		if (_node->_right)
		{
		   //如有右不为空,那么就去找到  右子树的最左路节点
			Node* subright = _node->_right;
			while (subright->_left)  subright = subright->_left; //找到最左路节点
			_node = subright;
		}
		else
		{
		   //右为空,沿着到根的路径,找孩子是父亲左的那个祖先
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && parent->_right == cur)
			{
				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 && parent->_left == cur)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}
};



//K是为了单独拿到key的类型 因为find erase 的接口都是key   而第二个模版参数T决定是这边传的是pair还是key
template<class K, class T,class KeyOfT>  //KeyofT 取出来比较的是k 还是pair中的k
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	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);
	}

	const_iterator begin() const
	{
		Node* cur = _root;
		while (cur && cur->_left)  cur = cur->_left;
		//找到最左路的节点
		return const_iterator(cur);
	}

	const_iterator end() const
	{
		return const_iterator(nullptr);
	}


	~RBTree()
	{
		_Destroy(_root);
		_root = nullptr;
	}

	Node* Find(const K& key)
	{
		Node* cur = _root;
		KeyOfT kot;//控制  是在pair中拿key还是直接拿key
		while (cur)
		{
			if (kot(cur->_data) < key) cur = cur->_right;  //我比你小,你往右找
			else if (kot(cur->_data) > key)  cur = cur->_left;  //我比你大,你往左找
			else return cur;
		}
		return nullptr;//说明找不到
	}

	//先用搜索树的逻辑插入节点,然后再去更新平衡因子。
	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;//控制  是在pair中拿key还是直接拿key
		//如果不为空树
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (kot(cur->_data) > kot(data)) //如果我比你大,到左子树去
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kot(cur->_data) < kot(data)) //比你小,你去右子树
			{
				parent = cur;
				cur = cur->_right;
			}
			else return make_pair(iterator(cur), false);//相等 
		}
		//此时肯定是对应地接在parent的后面
		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;
			//情况1,如果u为存在且为红
			if (grandfather->_left == parent)//如果p是g的左边,u就在右边
			{
				Node* uncle = grandfather->_right;
				//情况1,如果u为存在且为红 p u变黑,g变红 向上调整
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;
					//继续向上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else //情况2或者情况3, u为黑或者不存在   旋转+变色
				{
					if (cur == parent->_left) //情况2 右单旋+p变黑 g变红
					{
						//      g
						//   p    u
						// c
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else  //情况3 右左双旋  c变黑 g变红
					{
						//          g
							//   p     u
							//     c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;//情况2和情况3都要跳出循环
				}

			}
			else//if (grandfather->_right == parent)//如果p是g的右边,u就在左边    几乎一样,就是旋转的逻辑不同
			{
				Node* uncle = grandfather->_left;
				//情况1,如果u为存在且为红 p u变黑,g变红 向上调整
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;
					//继续向上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else//情况2或者情况3, u为黑或者不存在   旋转+变色
				{
					if (cur == parent->_right) //情况2 左单旋+p变黑 g变红
					{
						//      g
						//   p    u
						//          c
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else  //情况3 左右双旋  c变黑 g变红
					{
						//          g
							//   p     u
							//       c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;//情况2和情况3都要跳出循环
				}
			}
		}
		_root->_col = BLACK; //预防情况1出现 parent就是根的情况 此时无论如何_root变成黑,总没错
		 return make_pair(iterator(newnode), true);
	}

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

	bool IsBalance()
	{
		if (_root && _root->_col == RED)
		{
			cout << "根节点颜色是红色" << endl;
			return false;
		}
		int benchmark = 0;//找到一条路径作为基准值 然后看看其他路径是否相等
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				++benchmark;
			cur = cur->_left;
		}

		// 连续红色节点
		return _Check(_root, 0, benchmark);
	}

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


private:


	void _Destroy(Node* root)
	{
		if (root == nullptr) return;
		//后序遍历销毁
		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}

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

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

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

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

			return true;
		}

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

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

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

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

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



	//旋转代码和AVL树是一样的,只不过不需要搞平衡因子
	void RotateL(Node* parent)
	{
		//旋转前,先记录对应的节点
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* ppnode = parent->_parent;//子树的前驱节点
		//先让b变成30的边
		parent->_right = subRL;
		if (subRL) subRL->_parent = parent;
		//让30变成60的左边
		subR->_left = parent;
		parent->_parent = subR;
		//此时与前驱节点连接起来 如果前驱节点为空,直接改变根
		if (ppnode == nullptr)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		//如果前驱节点不为空,此时要根据之前paernt的情况决定插在哪边
		else
		{
			if (ppnode->_left == parent) ppnode->_left = subR;
			else ppnode->_right = subR;
			//向上连接
			subR->_parent = ppnode;
		}
	}


	void RotateR(Node* parent)
	{
		//旋转前,先记录对应的节点
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* ppnode = parent->_parent;//子树的前驱节点
		//先让b变成60的左边
		parent->_left = subLR;
		if (subLR) subLR->_parent = parent;
		//让60变成30的右边
		subL->_right = parent;
		parent->_parent = subL;
		//此时与前驱节点连接起来 如果前驱节点为空,直接改变根
		if (ppnode == nullptr)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		//如果前驱节点不为空,此时要根据之前paernt的情况决定插在哪边
		else
		{
			if (ppnode->_left == parent) ppnode->_left = subL;
			else ppnode->_right = subL;
			//向上连接
			subL->_parent = ppnode;
		}
	}

	Node* _root = nullptr;
};

二、set的模拟实现

前面我们已经将架子搭好了,这个时候就可以直接开始用了!!

namespace cyx
{
	template<class K>
	class set
	{
		struct SetKeyofT
		{ 
			const K& operator()(const K& key) //为了跟map保持一致
			{
				return key;
			}
		};
	public:
		typedef typename RBTree< K,K,SetKeyofT>::iterator  iterator;//在没有实例化的时候  编译器并不知道这是一个成员还是一个类型 typename可以帮助我们解决这个问题
		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;
	};


	

注意:

1、在没有实例化的时候 ,编译器并不知道这是一个成员还是一个类型 typename可以帮助我们解决这个问题

 2、对于insert返回值的改造,本质上是为了map去服务的,set只是配合而已。

三、map的模拟实现

3.1 insert的改装

在stl中 insert的返回值是pair<iterator,bool> 一开始我不太能理解为什么要这么设计。后来我明白了其实本质上为了后面重载[ ]的实现做铺垫。我们可以通过返回值去拿到iterator,并对对应节点的value进行直接修改!!

//先用搜索树的逻辑插入节点,然后再去更新平衡因子。
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;//控制  是在pair中拿key还是直接拿key
	//如果不为空树
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (kot(cur->_data) > kot(data)) //如果我比你大,到左子树去
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (kot(cur->_data) < kot(data)) //比你小,你去右子树
		{
			parent = cur;
			cur = cur->_right;
		}
		else return make_pair(iterator(cur), false);//相等 
	}
	//此时肯定是对应地接在parent的后面
	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;
		//情况1,如果u为存在且为红
		if (grandfather->_left == parent)//如果p是g的左边,u就在右边
		{
			Node* uncle = grandfather->_right;
			//情况1,如果u为存在且为红 p u变黑,g变红 向上调整
			if (uncle && uncle->_col == RED)
			{
				parent->_col = BLACK;
				uncle->_col = BLACK;
				grandfather->_col = RED;
				//继续向上调整
				cur = grandfather;
				parent = cur->_parent;
			}
			else //情况2或者情况3, u为黑或者不存在   旋转+变色
			{
				if (cur == parent->_left) //情况2 右单旋+p变黑 g变红
				{
					//      g
					//   p    u
					// c
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else  //情况3 右左双旋  c变黑 g变红
				{
					//          g
						//   p     u
						//     c
					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;//情况2和情况3都要跳出循环
			}

		}
		else//if (grandfather->_right == parent)//如果p是g的右边,u就在左边    几乎一样,就是旋转的逻辑不同
		{
			Node* uncle = grandfather->_left;
			//情况1,如果u为存在且为红 p u变黑,g变红 向上调整
			if (uncle && uncle->_col == RED)
			{
				parent->_col = BLACK;
				uncle->_col = BLACK;
				grandfather->_col = RED;
				//继续向上调整
				cur = grandfather;
				parent = cur->_parent;
			}
			else//情况2或者情况3, u为黑或者不存在   旋转+变色
			{
				if (cur == parent->_right) //情况2 左单旋+p变黑 g变红
				{
					//      g
					//   p    u
					//          c
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else  //情况3 左右双旋  c变黑 g变红
				{
					//          g
						//   p     u
						//       c
					RotateR(parent);
					RotateL(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;//情况2和情况3都要跳出循环
			}
		}
	}
	_root->_col = BLACK; //预防情况1出现 parent就是根的情况 此时无论如何_root变成黑,总没错
	 return make_pair(iterator(newnode), true);
}

3.2 重载[ ]的实现

V& operator[](const K& key)
{
	pair<iterator, bool> ret = _t.Insert(make_pair(key, V())); //默认构造
	return ret.first->second;
}

通过insert拿到对应位置的迭代器,然后指向其second 这样就可以直接进行修改了。 

3.3 模拟实现的代码

namespace cyx
{
	template<class K, class V>
	class map
	{
		struct MapKeyofT
		{
			const K& operator()(const pair<const K, V>& kv) //为了跟map保持一致
			{
				return kv.first;
			}
		};
	public:
		//模版类型的内嵌类型  加typename
		typedef typename RBTree<K, pair<const K, V>, MapKeyofT>::iterator  iterator;//在没有实例化的时候  编译器并不知道这是一个成员还是一个类型 typename可以帮助我们解决这个问题
		iterator begin()
		{
			return _t.begin();
		}

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

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = _t.Insert(make_pair(key, V())); //默认构造
			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;
	};

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

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

相关文章

第二篇:Python环境搭建:从初学者到专家

Python环境搭建&#xff1a;从初学者到专家 在编程的世界里&#xff0c;准备好一个高效而舒适的开发环境是走向成功的第一步。在这篇博客文章中&#xff0c;我们将一起探索如何为Python编程搭建一个理想的环境。无论你是完全的新手还是希望提升现有的技能&#xff0c;本文都会…

常用图像加密技术-流密码异或加密

异或加密是最常用的一种加密方式&#xff0c;广泛的适用于图像处理领域。这种加密方式依据加密密钥生成伪随机序列与图像的像素值进行异或操作&#xff0c;使得原像素值发生变化&#xff0c;进而使得图像内容发生变化&#xff0c;达到保护图像内容的目的。 该加密方法是以图像…

C语言程序设计(一)

1、指令、程序、软件 2、计算机语言&#xff1a;机器语言、汇编语言、高级语言 高级语言的发展&#xff1a;非结构化语言&#xff08;FORTRAN&#xff09;、结构化语言&#xff08;C语言&#xff09;、面向对象的语言&#xff08;C、面向对象&#xff09; 3、源程序、二进制…

在ubuntu 24.04 上安装vmware workstation 17.5.1

ubuntu安装在新组装的i9 14900机器上&#xff0c;用来学习笨叔的ARM64体系结构编程&#xff0c;也熟悉Linux的用法。但有时候写文档总是不方便&#xff0c;还是需要window来用。因此想在ubuntu 24.04上安装Linux版本的vmware worksation 17.5.1以虚拟机的方式安装windows 11。其…

Kubernetes学习笔记03

第八章、Kubernetes控制器Controller详解 Statefulset Statefulset主要是用来部署有状态应用 对于StatefulSet中的Pod&#xff0c;每个Pod挂载自己独立的存储&#xff0c;如果一个Pod出现故障&#xff0c;从其他节点启动一个同样名字的Pod&#xff0c;要挂载上原来Pod的存储…

Vitis HLS 学习笔记--AXI4 主接口

目录 1. 简介 2. 认识MAXI 3. MAXI突发操作 3.1 全局/本地存储器 3.2 MAXI优势与特点 3.3 查看MAXI报告 3.3.1 HW Interfaces 3.3.2 M_AXI Burst Information 3.4 MAXI 资源消耗 4. 理解 Volatile 4.1 标准C/C中的 volatile 4.2 HLS 中的 volatile 5. 总结 1. 简介…

CACTER AI实验室:AI大模型在邮件安全领域的应用

随着人工智能技术的飞速发展&#xff0c;AI已经深入到生活的各个领域。AI大模型在邮件安全领域展现出巨大潜力&#xff0c;尤其是反钓鱼检测上的应用&#xff0c;正逐渐展现出其独特的价值。 4月24日&#xff0c;CACTER AI实验室高级产品经理刘佳雄在直播交流会上分享了CACTER …

飞腾FT2000/4+银河麒麟全国产VPX架构 6U尺寸标准板卡,适用于船舶、交通等领域

XM-FT2000-VPX主板 XM-FT2000-VPX主板为VPX架构 6U尺寸标准板卡&#xff0c;提供的接口有DVI、USB、网络、UART、PCIE等接口。 处理器&#xff1a; FT2000/4四核国产处理器 芯片&#xff1a; 兆芯ZX-200芯片组 内存&#xff1a; 国产内存颗粒&#xff0c;双通道DDR4&#xff0…

应用场景:四大场景,用虚拟直播技术助力破圈

应用场景&#xff1a;四大场景用虚拟直播技术助力破圈 直播场景有四大类&#xff0c;看看你适合&#xff0c;哪一类场景的搭建&#xff1a; 1.教育型直播&#xff1a;寓教于货&#xff0c;文化浓厚&#xff1b; 人设&#xff1a;老师人设&#xff0c;以内容输出&#xff0c;“…

Python从0到100(十七):面向对象编程进阶

前言&#xff1a; 零基础学Python&#xff1a;Python从0到100最新最全教程。 想做这件事情很久了&#xff0c;这次我更新了自己所写过的所有博客&#xff0c;汇集成了Python从0到100&#xff0c;共一百节课&#xff0c;帮助大家一个月时间里从零基础到学习Python基础语法、Pyth…

AI Agent新对决:LangGraph与AutoGen的技术角力

AI Agent变革未来&#xff0c;LangGraph对抗AutoGen ©作者|Blaze 来源|神州问学 引言 比尔.盖茨曾在他的博客上发表一篇文章&#xff1a;《AI is about to completely change how you use computers》。在文章中&#xff0c;比尔盖茨探讨AI Agent对我们未来生活的巨大影…

hadoop命令

hadoop命令 目录 hadoop命令 1.查看文件下面有哪些文件和目录 2.获取文件信息 查看文件内容 3.创建一个文件夹 4.剪切 1&#xff09;从本地hadoop剪切到hdfs并上传到hdfs 2&#xff09;剪切 从hdfs剪切到本地hadoop目录上 5.删除 1&#xff09;递归删除 2&#xff0…

HotSpot JVM 为啥要叫做 HotSpot JVM?

1. Java与编译相关的三个概念&#xff1a; 首先了解三个概念 前端编译解释执行编译执行 ▌1.1、前端编译 编译器&#xff08;javac&#xff09;将源文件&#xff08;.java&#xff09;编译成java字节码文件&#xff08;.class&#xff09;的步骤是前端编译。 ▌1.2、解释执…

开放创新:蓝牙墨水屏标签,API接口助力,共创智慧新生态!

在当今科技日新月异的时代&#xff0c;蓝牙技术的发展为物联网应用带来了新的可能性。本文将探讨蓝牙墨水屏标签与API接口的应用&#xff0c;重点关注于串口协议、信号强度与广播频率自定义、蓝牙信标动态更改、蓝牙广播协议和开放定位数据等方面。具体场景包括设备资产显示标签…

python:reportlab 生成pdf:基本用法。

1.首先&#xff0c;打开cmd&#xff0c;安装reportlab pip install -i https://pypi.tuna.tsinghua.edu.cn/simple reportlab #从清华镜像安装更快 然后就可以使用其基本用法。 from reportlab.lib.pagesizes import letter from reportlab.pdfgen import canvasdef genera…

【鸿蒙应用】理财App

目录 第一节项目讲解项目介绍 第二节&#xff1a;项目创建登录静态框架编写登录页面设稿新建项目控制台添加项目Login页面封装标题组件 第三节&#xff1a;登录页静态表单编写第四节—内容页架构分析底部栏组件第五节—底部栏组件切换第六节&#xff1a;首页静态页编写第七节&a…

【MySQL 数据宝典】【索引原理】- 004 优化示例-join in exist

一、join 优化原理 1.1 基本连接方式介绍 JOIN 是 MySQL 用来进行联表操作的&#xff0c;用来匹配两个表的数据&#xff0c;筛选并合并出符合我们要求的结果集。 1.2 驱动表的定义 1.2.1 什么是驱动表 多表关联查询时,第一个被处理的表就是驱动表,使用驱动表去关联其他表.驱…

笔记:能量谱密度与功率谱密度(二)

目录 一、ESD与PSD的定义、单位、性质 二、对ESD与PSD的直观理解 三、总结&#xff1a; 某物理量的“分布”在离散系统中&#xff0c;各点(纵坐标含义&#xff09;的物理意义仍然是该物理量&#xff0c;而在连续系统中&#xff0c;各点&#xff08;纵坐标含义&#xff09;的物…

注意力机制略解

引子 例如&#xff0c;现在需要拟合函数f(x)&#xff0c;我们已知函数上的若干点&#xff08;xi&#xff0c;yi&#xff09; 现在我们想知道在自变量取x’的时候&#xff0c;函数值y’为多少 正常的思路比如拉格朗日插值&#xff0c;牛顿插值&#xff0c;直接去估计函数的表…

Linux网络服务-DHCP

一、DHCP工作原理 DHCP&#xff08;Dynamic Host Configuration Protocol&#xff0c;动态主机配置协议&#xff09;&#xff1a;用于自动获取IP地址 1.客户端会发送一个广播DHCP Discover报文去寻找DHCP服务器 2.客户端只会接收第一个回复的DHCP服务器的报文 3.服务器会发…