【C++历练之路】红黑树——map与set的封装实现

news2024/11/22 21:06:50

W...Y的个人主页💕

gitee代码仓库分享😊 

 


前言:上篇博客中,我们为了使二叉搜索树不会出现”一边倒“的情况,使用了AVL树对搜索树进行了处理,从而解决了数据在有序或者接近有序时出现的情况。但是AVL树还会有一大缺陷就是其性能的原因,当我们在使其满足AVL树的规则时,其付出的旋转代价是非常大的,所以经常修改的结构就不适合AVL树。但是红黑树就可以补足AVL树的缺陷。

目录

1. 红黑树

1.1 红黑树的概念

1.2 红黑树的性质 

 1.3红黑树节点的定义

 1.4红黑树的插入

1.5 红黑树与AVL树的比较 

2. 红黑树模拟实现STL中的map与set

2.1 红黑树的迭代器

2.2 红黑树的改写与迭代器完整代码

2.3 map的封装

2.4 set的封装


1. 红黑树

1.1 红黑树的概念

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

假设最短路径为h,则最长路径为2h。 

1.2 红黑树的性质 

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

为什么满足上述性质,红黑树就可以保证其最长路径的节点树不会超过最短路径的两倍呢?  

 前两个性质非常通俗易懂,我们从第三个性质开始解读:

3.没有连续的两个红色节点。

4.每条路径的黑色节点数是相同的。

所以我们就可以假设一颗红黑树中每条路径有两个黑色节点(满足性质4),那最短路径只可能是全黑节点,最长路径一定是一黑一红节点(假设最短路径与最长路径都存在),那么红色节点只能在黑色节点中间插入,这样才能满足性质3,所以红黑树就可以保证其最长路径的节点树不会超过最短路径的两倍。

对比AVL树与红黑树的结构,其AVL树的高度近似logN,而红黑树的高度近似2logN,所以相对于AVL树,红黑树的搜索效率差一些,但是几乎可以忽略不计,因为logN足够小,所以他们之间的搜索差距微乎其微。

 1.3红黑树节点的定义

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

在节点定义时我们默认将节点设置成红色节点,这样如果出现连续的红色节点时,我们可以进行变色操作,通过维护一个子树来使红黑树合法,但是如果插入一个黑色节点时,我们就无法下手,因为每条路的黑色节点数必须相同,这样我们无法很好的进行操作使其合法化。 

 1.4红黑树的插入

 红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

1. 按照二叉搜索的树规则插入新节点:

class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
    bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(kv); // 红色的
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;
}
private:
	Node* _root = nullptr;
};

2. 检测新节点插入后,红黑树的性质是否造到破坏:
因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何
性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连
在一起的红色节点,此时需要对红黑树分情况来讨论:
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点 

 情况一: cur为红,p为红,g为黑,u存在且为红

解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。 

情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑

 如果u存在且为黑, 则cur一定不是新增节点,因为这样就不满足性质4:每条路径黑色节点个数相同。所以我们将上面图补充完整就是下图所示,先是由情况一进行调整,然后向上调整后才得到上图。所以情况一向上调整后的情况不一定又是情况一!!

这时我们就无法用情况一的做法进行调整, 如果继续用情况一法则已经违反规则了。左右极度不均衡只能进行选择,这时我们就可以类比使用AVL树中的旋转法则。(旋转+变色)

p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,
p为g的右孩子,cur为p的右孩子,则进行左单旋转
p、g变色--p变黑,g变红

在变色时我们不区分其p与u节点的左右,但是在旋转时我们就要进行区分。

情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑 

 这种情况并不是一边高,而是两边都高左边高右边也高。所以我们得使用左右/右左双旋转。

p为g的左孩子,cur为p的右孩子,左右双旋+变色

p为g的右孩子,cur为p的左孩子,右左双旋+变色

针对每种情况进行相应的处理即可。

bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(kv); // 红色的
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				// 情况一:叔叔存在且为红
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					// 情况二:叔叔不存在或者存在且为黑
					// 旋转+变色
					if (cur == parent->_left)
					{
						//       g
						//    p    u
						// c
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//       g
						//    p     u
						//      c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				} 
			}
			else
			{
				Node* uncle = grandfather->_left;
				// 情况一:叔叔存在且为红
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					// 情况二:叔叔不存在或者存在且为黑
					// 旋转+变色
					//      g
					//   u     p
					//            c
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//		g
						//   u     p
						//      c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;

		return true;
	}

	void RotateL(Node* parent)
	{
		++rotateSize;

		Node* subR = parent->_right;
		Node* subRL = subR->_left;

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

		subR->_left = parent;
		Node* ppnode = parent->_parent;
		parent->_parent = subR;

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

	void RotateR(Node* parent)
	{
		++rotateSize;

		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		subL->_right = parent;

		Node* ppnode = parent->_parent;
		parent->_parent = subL;

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

1.5 红黑树与AVL树的比较 

 红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O($log_2 N$),红黑树不追
求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,
所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红
黑树更多。

2. 红黑树模拟实现STL中的map与set

我们模拟实现了红黑树,接下来就是对map与set的封装。

我们通过观察其stl源码切入进行仿写:

 我们发现无论是map还是set都复用的一个红黑树,模板参数都是k,v模型。通过源码我们可以发现:set<k> -> rb_tree<k,k> map<k,v> ->rb_tree<k,pair<const k,v>>。所以节点中存什么内容是由v决定的,不是k决定的。将红黑树写成泛型,所以我们必须将上面写的红黑树的模板进行修改!

进行封装后就可以解决复用红黑树的模板。 

 但是使用红黑树的模板时,set和map所比较的对象不一样,因为set比较的就是key,而map比较的是value,所以我们就得使用仿函数进行操作,我们创建keyoft仿函数取出T对象中的key即可。

//set
struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

//map
struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};

2.1 红黑树的迭代器

首先我们得封装一个红黑树的迭代器用来实现++、--、*、->、!=。这里的迭代器与list的迭代器非常类似,可以用一个指针来实现其内容。但是++与--必须确定下一个与上一个节点的关系,这里我们可以使用中序遍历解决,但是得用一个栈来辅助,这里我们不想使用这样的方法,我们可以找规律来实现:

++逻辑

1.it指向节点,右不为空,下一个就是右子树的最左节点

2.it指向节点,右为空,意味着这个节点的子树中序访问完了,下一个节点找祖先里面的孩子==父亲左的那个祖先。

--逻辑与++相反!

这样迭代器就很好解决了:

template<class T>
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTreeIterator<T> Self;

	Node* _node;

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

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

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

	Self& operator++()
	{
		if (_node->_right)
		{
			// 右子树的中序第一个(最左节点)
			Node* subLeft = _node->_right;
			while (subLeft->_left)
			{
				subLeft = subLeft->_left;
			}

			_node = subLeft;
		}
		else
		{
			// 祖先里面孩子是父亲左的那个
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = cur->_parent;
			}

			_node = parent;
		}

		return *this;
	}

	Self& operator--()
	{
		// 
		return *this;
	}

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

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

迭代器的好处是可以方便遍历,是数据结构的底层实现与用户透明。如果想要给红黑树增加迭代
器,需要考虑以前问题:

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

typedef RBTreeIterator<T> iterator;

iterator begin()
{
	Node* subLeft = _root;
	while (subLeft && subLeft->_left)
	{
		subLeft = subLeft->_left;
	}

	return iterator(subLeft);
}

iterator end()
{
	return iterator(nullptr);
}
//map
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
iterator begin()
{
	return _t.begin();
}

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

bool insert(const pair<K, V>& kv)
{
	return _t.Insert(kv);
}
//set

typedef typename RBTree<K, const K, SetKeyOfT>::iterator iterator;

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

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

bool insert(const K& key)
{
	return _t.Insert(key);
}

2.2 红黑树的改写与迭代器完整代码

#pragma once
#include<vector>

enum Colour
{
	RED,
	BLACK
};

template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	Colour _col;
	T _data;

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

template<class T>
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTreeIterator<T> Self;

	Node* _node;

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

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

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

	Self& operator++()
	{
		if (_node->_right)
		{
			// 右子树的中序第一个(最左节点)
			Node* subLeft = _node->_right;
			while (subLeft->_left)
			{
				subLeft = subLeft->_left;
			}

			_node = subLeft;
		}
		else
		{
			// 祖先里面孩子是父亲左的那个
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = cur->_parent;
			}

			_node = parent;
		}

		return *this;
	}

	Self& operator--()
	{
		// 
		return *this;
	}

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

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

// set->RBTree<K, K, SetKeyOfT>
// map->RBTree<K, pair<K, V>, MapKeyOfT>

// KeyOfT仿函数 取出T对象中的key
template<class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	typedef RBTreeIterator<T> iterator;

	iterator begin()
	{
		Node* subLeft = _root;
		while (subLeft && subLeft->_left)
		{
			subLeft = subLeft->_left;
		}

		return iterator(subLeft);
	}

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

	bool Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return true;
		}

		KeyOfT kot;
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kot(cur->_data) > kot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(data); // 红色的
		if (kot(parent->_data) < kot(data))
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				// 情况一:叔叔存在且为红
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					// 情况二:叔叔不存在或者存在且为黑
					// 旋转+变色
					if (cur == parent->_left)
					{
						//       g
						//    p    u
						// c
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//       g
						//    p     u
						//      c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
			else
			{
				Node* uncle = grandfather->_left;
				// 情况一:叔叔存在且为红
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					// 情况二:叔叔不存在或者存在且为黑
					// 旋转+变色
					//      g
					//   u     p
					//            c
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//		g
						//   u     p
						//      c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;

		return true;
	}

	void RotateL(Node* parent)
	{

		Node* subR = parent->_right;
		Node* subRL = subR->_left;

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

		subR->_left = parent;
		Node* ppnode = parent->_parent;
		parent->_parent = subR;

		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		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;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		subL->_right = parent;

		Node* ppnode = parent->_parent;
		parent->_parent = subL;

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

private:
	Node* _root = nullptr;
};

2.3 map的封装

namespace why
{
	template<class K, class V>
	class map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;

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

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

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

	private:
		RBTree<K, pair<const K, V>, MapKeyOfT> _t;
	};
}

2.4 set的封装

#include"RBTree.h"
namespace why
{
	template<class K>
	class set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename RBTree<K, const K, SetKeyOfT>::iterator iterator;

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

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

		bool insert(const K& key)
		{
			return _t.Insert(key);
		}

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

 

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

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

相关文章

Apple 添加了 13 英寸 iPad Air

劈啪&#xff01;苹果推出的新款 iPad Air&#xff0c;将所有梦想变为现实&#xff01;它配备了强大的后置 12MP 摄像头和前置 12MP 摄像头&#xff0c;令您的拍摄体验更加出色。苹果还加入了 Apple Pencil 悬停功能&#xff0c;让您的创作更加灵活。 这款 iPad Air 不仅速度加…

武汉凯迪正大—电能质量测试仪功能特点

武汉凯迪正大电能质量测试仪功能特点 1. 仪器是专门用于检测电网中发生波形畸变、谐波含量、三相不平衡等电能质量问题的高精度测试仪器&#xff1b;同时还具备电参量测试、矢量分析的功能。 2. 可精确测量电压、电流、有功功率、无功功率、相角、功率因数、频率等多种电参量…

PowerBI实用技巧——案例十一 (根据所选日期动态滚动显示日期)

分享一波常用的日期动态滚动显示的小技巧,类似的方法还有很多&#xff0c;这里举3个小案例参考。 一、根据所选日期滚动显示近12月的数据&#xff08;日期为年月格式&#xff09; 效果图&#xff1a; 实现逻辑&#xff1a; rolloing_month //所选日期为年月格式,根据所选日…

为什么跑腿越来越受到年轻人的青睐

跑腿服务越来越受到年轻人的青睐&#xff0c;主要源于以下几个方面的原因&#xff1a; 1. 便捷快速&#xff1a;在快节奏的现代生活中&#xff0c;年轻人追求的是效率和速度。跑腿服务提供了一种即时、便捷的解决方案&#xff0c;使他们能够在繁忙的生活和工作中节省时间和精力…

AI中转计费平台系统源码

AI中转计费平台系统源码 源码免费下载地址抄笔记 (chaobiji.cn)

如何正确理解RestTemplate远程调用的实现原理?

本文从源码出发理解RestTemplate实现远程调用的底层原理。 初始化RestTemplate实例 我们可以通过RestTemplate所提供的几个构造函数来对其进行初始化。在分析这些构造函数之前&#xff0c;有必要先看一下RestTemplate类的定义&#xff0c;如下所示&#xff1a; public class …

OC5864 0.6A输出 60V输入 500KHZ DCDC降压转换IC

一级代理 技术支持 提供样品测试 Tel&#xff1a;18028786817 简介 OC5864是一款内置功率MOSFET的单片降压型开关模式转换器。OC5864在5.5~60V宽输入电源范围内实现0.6A峰值输出电流&#xff0c;并且具有出色的线电压和负载调整率。 OC5864采用PWM电流模工作模式&#xff0c;…

技术分享 | i.MX8M Mini适配MIPI转eDP芯片

1.方案概述 此方案使用HD-8MMN-CORE的核心板搭配TI公司的芯片SN65DSI86转换芯片实现。 SN65DSI86作为一款MIPI DSI转eDP的芯片&#xff0c;支持双通道DSI输入&#xff0c;最大四通道显示输出&#xff0c;最大支持4K60fps输出&#xff0c;WUXGA 1080P。本方案中将采用单通道DS…

Vue从入门到实战Day01

一、Vue快速上手 1. vue概念 概念&#xff1a;Vue是一个用于 构建用户界面的 渐进式 框架 构建用户界面&#xff1a;基于数据动态渲染页面渐进式&#xff1a;循序渐进的学习框架&#xff1a;一套完整的项目解决方案&#xff0c;提升开发效率 优点&#xff1a;大大提升开发效…

[SWPUCTF 2021 新生赛]PseudoProtocols、[SWPUCTF 2022 新生赛]ez_ez_php

[SWPUCTF 2021 新生赛]PseudoProtocols 打开环境&#xff0c;提示hint.php就在这里&#xff0c;且含有参数wllm 尝试利用PHP伪协议读取该文件 ?wllmphp://filter/convert.base64-encode/resourcehint.php//文件路径php://filter 读取源代码并进行base64编码输出。 有一些敏…

哪款充电宝质量和口碑比较好?适合入手充电宝有哪些?

充电宝这么好用的移动电源就不用我说了吧&#xff0c;平时不出门还好&#xff0c;家里有插座可以充电&#xff0c;但是但凡出门了&#xff0c;手机电量一般是不能够支撑到&#xff0c;像我这种手机重度使用者&#xff0c;出门在外手机快没电了我就非常焦虑了&#xff0c;有一款…

在VMware上利用Samba实现资源共享

一、背景 FTP协议能让主机之间的文件传输变得简单方便&#xff0c;但是FTP协议的本质是传输文件。 举个栗子&#xff1a;当客户端想修改服务器上的test.txt&#xff0c;需要先get test.txt将文件下载下来&#xff0c;修改后再put test.txt 有没有一种方式能使客户端直接修改…

Tansformer原理解读

什么是注意力机制 生物学中的注意力机制是指人类或动物能够选择性地将感知和认知资源集中到某些信息或任务上的能力。这种能力允许我们在众多信息的背景中过滤出重要的信息&#xff0c;并将这些信息传递给相应的神经元进行处理。 本质&#xff1a;能从中抓住重点&#xff0c;…

“芯”心相“蜥” 共筑未来!龙蜥社区走进兆芯 MeetUp 圆满结束

4 月 26 日&#xff0c;以“芯”心相“蜥” 共筑未来 为主题的龙蜥社区「走进系列」之走进兆芯 MeetUp 在上海成功召开。来自统信软件、联和东海、众新科技等企业参会代表共聚兆芯&#xff0c;介绍了当前企业产品和技术创新等方面的最新成果&#xff0c;并围绕社区发展建设、行…

【linux】dmesg工具

dmesg介绍 dmesg工具用途&#xff1a; dmesg - print or control the kernel ring buffer kernel ring buffer, 内核环形缓冲区&#xff0c;也叫环形队列&#xff0c;Linux内核日志就存储在一个环形队列中&#xff0c;环形队列满的时候&#xff0c;新的消息会覆盖掉旧的消息。…

如何实现“人岗匹配”?华恒智信人力资源咨询公司这样做

【客户评价 为了合理有效地评价员工&#xff0c;真正实现员工的“人岗匹配”&#xff0c;我公司提出了对公司人员展开人才测评的项目需求。经过对多家咨询公司的仔细挑选&#xff0c;我们最终选择了专业的人力资源咨询公司——华恒智信作为我们的项目合作伙伴。 在项目咨询中&a…

内网穿透使用教程

什么是内网穿透 内网穿透&#xff0c;即NAT穿透&#xff0c;网络连接时术语&#xff0c;计算机是局域网内时&#xff0c;外网与内网的计算机节点需要连接通信&#xff0c;有时就会出现不支持内网穿透。就是说映射端口&#xff0c;能让外网的电脑找到处于内网的电脑&#xff0c…

Star15.3k,开源数据可视化分析工具项目

好东西来了&#xff0c;这是一个人人可用的开源数据可视化分析工具项目&#xff0c;V 哥迫不及待的要给大家推荐这个项目&#xff0c;帆软、Tableau 等商业 BI 工具的开源替代&#xff0c;已在 Github 上被 Star了15.3k了&#xff0c;大家一起来了解一下。自己搭建起来可用&…

酷开科技 |酷开系统,给家里多点乐趣~

作为家庭娱乐的核心枢纽&#xff0c;酷开系统致力于为每一个家庭带来更多的乐趣和欢笑。通过其智能化的设计和个性化的服务&#xff0c;酷开系统正在逐渐改变家庭娱乐的方式&#xff0c;让客厅成为家中温馨的娱乐中心。 首先&#xff0c;酷开系统的界面友好而直观&#xff0c;…

【SSL证书】免费单域名SSL证书怎么申请

1、访问证书颁发机构&#xff08;CA&#xff09;&#xff1a;比如JoySSL、ZeroSSL、各大云平台等&#xff0c;这些机构提供免费的SSL证书申请&#xff0c;并支持单域名证书。在网站上注册账号&#xff0c;并根据提示选择单域名证书进行申请。 &#xff01;&#xff01;&#xf…