【C++】用红黑树模拟实现set与map

news2024/10/12 6:13:51

目录

一、红黑树的完善:

1、红黑树节点模版的修改:

2、仿函数在模拟实现中的应用:

3、新增迭代器:

4、红黑树中的迭代器实现:

二、set与map的模拟实现:

1、insert:

2、map的[ ]:

三、测试:

四、完整代码:


红黑树初始代码:

#pragma once
#include<iostream>

using namespace std;

enum COLOR
{
	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;
	COLOR 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:
	bool Insert(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->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		//走到这就是找到了
		cur = new Node(kv);
		cur->col = RED;
		//再连接到这个红黑树中
		if (parent->_kv.first > cur->_kv.first)
		{
			parent->_left = cur;
		}
		else//parent->_kv.first < cur->_kv.first
		{
			parent->_right = cur;
		}
		cur->_parent = parent;
		//已经连接完成后
		while (parent && parent->col == RED)
		{
			//这里祖父必定存在,因为如果进循环后parent就是red,然而red不可能为根节点,所以parent的parent必定存在
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				if (uncle && uncle->col == RED)
				{
					//修改颜色
					uncle->col = BLACK;
					parent->col = BLACK;
					grandfather->col = RED;
					//修改cur
					cur = grandfather;
					//修改parent继续指向cur的parent
					parent = cur->_parent;
				}
				else//uncle不存在或者uncle为黑色就需要旋转了
				{
					if (cur == parent->_left)
					{
						RotateR(grandfather);
						parent->col = BLACK;
						grandfather->col = RED;
					}
					else//cur == parent->_right
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->col = BLACK;
						grandfather->col = RED;
					}
					break;
				}
			}
			else//parent == grandfather->_right
			{
				Node* uncle = grandfather->_left;
				if (uncle && uncle->col == RED)
				{
					//修改颜色
					uncle->col = BLACK;
					parent->col = BLACK;
					grandfather->col = RED;
					//修改cur
					cur = grandfather;
					//修改parent继续指向cur的parent
					parent = cur->_parent;
				}
				else//uncle不存在或者uncle为黑色就需要旋转了
				{
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->col = BLACK;
						grandfather->col = RED;
					}
					else//cur == parent->_right
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->col = BLACK;
						grandfather->col = RED;
					}
					break;
				}
			}
		}
		_root->col = BLACK;
		return true;
	}

	//左单旋
	void RotateL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;

		parent->_right = curleft;
		if (curleft)
		{
			curleft->_parent = parent;
		}
		cur->_left = parent;

		//将parent的父节点保存起来
		Node* pparent = parent->_parent;
		parent->_parent = cur;

		if (parent == _root)
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (pparent->_kv.first < cur->_kv.first)
			{
				pparent->_right = cur;
			}
			else //if (pparent->_kv.first > cur->_kv.first)
			{
				pparent->_left = cur;
			}
			cur->_parent = pparent;
		}
	}

	//右单旋
	void RotateR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;

		parent->_left = curright;
		if (curright)
		{
			curright->_parent = parent;
		}
		cur->_right = parent;

		//将parent的父节点保存起来
		Node* pparent = parent->_parent;
		parent->_parent = cur;

		if (parent == _root)
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (pparent->_kv.first < cur->_kv.first)
			{
				pparent->_right = cur;
			}
			else //if (pparent->_kv.first > cur->_kv.first)
			{
				pparent->_left = cur;
			}
			cur->_parent = pparent;
		}
	}

	bool CheckColor(Node* root, int blacknum, int benchmark)
	{
		if (root == nullptr)
		{
			if (blacknum != benchmark)
			{
				cout << "黑色节点的数量不匹配" << endl;
				return false;
			}
			return true;
		}
		if (root->col == BLACK)
		{
			++blacknum;
		}

		if (root->col == RED && root->_parent && root->_parent->col == RED)
		{
			cout << root->_kv.first << "出现连续红色节点" << endl;
			return false;
		}
		return CheckColor(root->_left, blacknum, benchmark)
			&& CheckColor(root->_right, blacknum, benchmark);
	}

	bool IsRBTree()
	{
		return _IsRBTree(_root);
	}

	bool _IsRBTree(Node* root)
	{
		if (root == nullptr)
			return true;
		if (root->col == RED)
		{
			return false;
		}

		//基准值
		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->col == BLACK)
				++benchmark;
			cur = cur->_left;
		}
		return CheckColor(root, 0, benchmark);
	}
private:
	Node* _root = nullptr;
};

一、红黑树的完善:

1、红黑树节点模版的修改:

这里要将红黑树节点的模版从两个修改为一个T,这是首先通过set或者map的模版参数传给红黑树的第二个模版参数进行实例化Node。

2、仿函数在模拟实现中的应用:

再节点中存储的是T的,在set中这个T是K结构的,在进行比较的时候,直接访问比较就可以了,但是在map中这个T是K,V结构的键值对,在进行比较的时候,就不能够直接比较,这个时候就可以通过仿函数(通过对一个类进行()的重载,使这个类在使用的时候看上去像一个函数)取这个键值对中的first来进行比较即可。

所以在传模版的时候就需要第三个模版参数来进行仿函数的控制。

map中的仿函数实现:

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

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

set中的仿函数的实现:

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

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

仿函数在比较中的应用:

上面是部分代码的修改:

下面是仿函数在红黑树中的使用思路:

如上所示:

通过在自我实现的map或者是set中进行仿函数的实现,在set中返回key值即可,在map中返回这个键对值的first即可。

这样在比较的时候就可以实现map的大小比较了。

3、新增迭代器:

将红黑树的节点进行再一次的封装,这样封装出一个迭代器的类,应该是只有一个模版参数T但是要实现const迭代器和普通迭代器要使用这个迭代器的类,这样再加上两个模版参数来进行迭代器的实现。

template<class T, class Ptr, class Ref>
struct __TreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __TreeIterator<T, Ptr, Ref> 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;
	}

++操作

思路:

首先进行此判断,当前迭代器所指向的位置进行++操作后,要么在右子树的最小节点或者是往祖先找,下一个节点要是一个父节点的左节点。

那么就进行判断:

如果此时迭代器所指向的节点右边存在子树,那么就直接找到右子树的最左节点给node最后进行返回*this.

如果此时迭代器所指向的节点右边不存在子树,那么就往祖先找,直到找到孩子是父节点左的那个节点,就是下一个要访问的节点。

Self& operator++()
{
	if (_node->_right)
	{
		//右子树不是空,就找右子树的最小节点(最左边的节点)
		Node* RightTree = _node->_right;
		while (RightTree->_left)
		{
			RightTree = RightTree->_left;
		}
		_node = RightTree;
	}
	else
	{
		//右为空,就找孩子是父节点的左孩子的那个父节点
		Node* cur = _node;
		Node* parent = cur->_parent;
		while (parent && cur == parent->_right)
		{
			cur = cur->_parent;
			parent = parent->_parent;
		}
		_node = parent;
	}
	return *this;
}

--操作:

思路:

首先进行此判断,当前迭代器所指向的位置进行--操作后,要么在左子树的最大节点或者是往祖先找,下一个节点要是一个父节点的右节点。

如果迭代器所指向的节点的左子树不为空,则--操作后应该找到其左子树当中的最右结点。
如果迭代器所指向的节点的左子树为空,则--操作后应该在该结点的祖先结点中,找到孩子不在父亲左的祖先。

那么就进行判断:

如果此时迭代器所指向的节点左边存在子树,那么就直接找到左子树的最右节点给node最后进行返回*this.

如果此时迭代器所指向的节点左边不存在子树,那么就往祖先找,直到找到孩子是父节点右的那个节点,就是下一个要访问的节点。

Self& operator--()
{
	if (_node->_left)
	{
		Node* RootRight = _node->_left;
		while (RootRight->_right)
		{
			RootRight = RootRight->_right;
		}
		_node = RootRight;
	}
	else
	{
		//要找到孩子是父亲的右边的那个节点就是--后的值
		Node* cur = _node;
		Node* parent = _node->_parent;
		while (parent && cur == parent->_left)
		{
			cur = cur->_parent;
			parent = parent->_parent;
		}
		_node = parent;
	}
	return *this;
}

4、红黑树中的迭代器实现:

既然有了迭代器这个类,那么在红黑树中就可以进行begin和end的实现:

template<class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T>  Node;
public:
	typedef __TreeIterator<T, T*, T&> iterator;
	typedef __TreeIterator<T, const T*, const T&> const_iterator;

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

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

	const_iterator begin() const
	{
		Node* leftmin = _root;
		while (leftmin && leftmin->_left)
		{
			leftmin = leftmin->_left;
		}
		return const_iterator(leftmin);
	}

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

二、set与map的模拟实现:

在下面我们需要修改insert的实现,接着通过insert来进行map中的[ ]的实现。

1、insert:

首先将insert的返回值修改为iterator和bool的键对值,接着将实现中的return都修改一下:

在修改RBTree中的insert之后,也要对map和set中的insert进行修改:

map中:

直接进行调用即可

set中:

不能够直接进行调用,因为存在迭代器不匹配的问题,那么看看STL标准模板库中的解决方式:

上述是在set中的标准库中的实现,先直接抄过来看看:

可以知道,将set的insert实现的代码从直接调用变成上述中就可以编译通过,解决方法:

那么就需要在迭代器类中增加一个构造函数

当这个类被实例化成const迭代器的时候,这就是一个普通构造,是普通迭代器构造const迭代器,但是如果这个类被实例化成普通迭代器的时候,新增的就是一个拷贝构造(普通迭代器构造普通迭代器)

2、map的[ ]:

三、测试:

int main()
{
	ppr::map<int, int> m;
	m.Insert(make_pair(1, 1));
	m.Insert(make_pair(3, 3));
	m.Insert(make_pair(2, 2));

	ppr::map<int, int>::iterator mit = m.begin();
	while (mit != m.end())
	{
		// 不能修改key,可以修改value
		//mit->first = 1;
		mit->second = 2;
		cout << mit->first << ":" << mit->second << endl;
		++mit;
	}
	cout << endl;

	ppr::set<int> s;
	s.Insert(5);
	s.Insert(2);
	s.Insert(2);
	s.Insert(12);
	s.Insert(22);
	s.Insert(332);
	s.Insert(7);
	ppr::set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	for (const auto& e : s)
	{
		cout << e << " ";
	}
	cout << endl;

	ppr::map<string, string> dict;
	dict.Insert(make_pair("sort", "111"));
	dict["apple"]; // 插入

	for (const auto& kv : dict)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
	cout << endl;

	dict["apple"] = "苹果"; // 修改
	dict["sort"] = "222"; // 修改
	dict["pear"] = "梨"; // 插入+修改

	for (const auto& kv : dict)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
	cout << endl;
	return 0;
}

四、完整代码:

红黑树:

#pragma once
#include<iostream>
using namespace std;
enum COLOR
{
	RED,
	BLACK
};

template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data;
	COLOR col;

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

template<class T, class Ptr, class Ref>
struct __TreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __TreeIterator<T, Ptr, Ref> Self;

	typedef __TreeIterator<T, T*, T&> iterator;

	__TreeIterator(const iterator& it)
		:_node(it._node)
	{}

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

	Self& operator--()
	{
		if (_node->_left)
		{
			Node* RootRight = _node->_left;
			while (RootRight->_right)
			{
				RootRight = RootRight->_right;
			}
			_node = RootRight;
		}
		else
		{
			//要找到孩子是父亲的右边的那个节点就是--后的值
			Node* cur = _node;
			Node* parent = _node->_parent;
			while (parent && cur == parent->_left)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}

	Self& operator++()
	{
		if (_node->_right)
		{
			//右子树不是空,就找右子树的最小节点(最左边的节点)
			Node* RightTree = _node->_right;
			while (RightTree->_left)
			{
				RightTree = RightTree->_left;
			}
			_node = RightTree;
		}
		else
		{
			//右为空,就找孩子是父节点的左孩子的那个父节点
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_right)
			{
				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 __TreeIterator<T, T*, T&> iterator;
	typedef __TreeIterator<T, const T*, const T&> const_iterator;

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

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

	const_iterator begin() const
	{
		Node* leftmin = _root;
		while (leftmin && leftmin->_left)
		{
			leftmin = leftmin->_left;
		}
		return const_iterator(leftmin);
	}

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

	Node* Find(const K& key)
	{
		Node* cur = _root;
		KeyOfT kot;
		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);
		}
		Node* parent = nullptr;
		Node* cur = _root;
		Node* newnode = cur;
		//找到要插入的位置
		KeyOfT kot;
		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);
			}
		}
		//走到这就是找到了
		cur = new Node(data);
		cur->col = RED;
		//再连接到这个红黑树中
		if (kot(parent->_data) > kot(cur->_data))
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;
		//已经连接完成后
		while (parent && parent->col == RED)
		{
			//这里祖父必定存在,因为如果进循环后parent就是red,然而red不可能为根节点,所以parent的parent必定存在
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				if (uncle && uncle->col == RED)
				{
					//修改颜色
					uncle->col = BLACK;
					parent->col = BLACK;
					grandfather->col = RED;
					//修改cur
					cur = grandfather;
					//修改parent继续指向cur的parent
					parent = cur->_parent;
				}
				else//uncle不存在或者uncle为黑色就需要旋转了
				{
					if (cur == parent->_left)
					{
						RotateR(grandfather);
						parent->col = BLACK;
						grandfather->col = RED;
					}
					else//cur == parent->_right
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->col = BLACK;
						grandfather->col = RED;
					}
					break;
				}
			}
			else//parent == grandfather->_right
			{
				Node* uncle = grandfather->_left;
				if (uncle && uncle->col == RED)
				{
					//修改颜色
					uncle->col = BLACK;
					parent->col = BLACK;
					grandfather->col = RED;
					//修改cur
					cur = grandfather;
					//修改parent继续指向cur的parent
					parent = cur->_parent;
				}
				else//uncle不存在或者uncle为黑色就需要旋转了
				{
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->col = BLACK;
						grandfather->col = RED;
					}
					else//cur == parent->_right
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->col = BLACK;
						grandfather->col = RED;
					}
					break;
				}
			}
		}
		_root->col = BLACK;
		return make_pair(iterator(newnode), true);
	}

	//左单旋
	void RotateL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;

		parent->_right = curleft;
		if (curleft)
		{
			curleft->_parent = parent;
		}
		cur->_left = parent;

		//将parent的父节点保存起来
		Node* pparent = parent->_parent;
		parent->_parent = cur;

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

			}
			cur->_parent = pparent;
		}
	}

	//右单旋
	void RotateR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;

		parent->_left = curright;
		if (curright)
		{
			curright->_parent = parent;
		}
		cur->_right = parent;

		//将parent的父节点保存起来
		Node* pparent = parent->_parent;
		parent->_parent = cur;

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

	bool CheckColor(Node* root, int blacknum, int benchmark)
	{
		if (root == nullptr)
		{
			if (blacknum != benchmark)
			{
				cout << "黑色节点的数量不匹配" << endl;
				return false;
			}
			return true;
		}
		if (root->col == BLACK)
		{
			++blacknum;
		}

		if (root->col == RED && root->_parent && root->_parent->col == RED)
		{
			cout << root->_kv.first << "出现连续红色节点" << endl;
			return false;
		}
		return CheckColor(root->_left, blacknum, benchmark)
			&& CheckColor(root->_right, blacknum, benchmark);
	}

	bool IsRBTree()
	{
		return _IsRBTree(_root);
	}

	bool _IsRBTree(Node* root)
	{
		if (root == nullptr)
			return true;
		if (root->col == RED)
		{
			return false;
		}

		//基准值
		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->col == BLACK)
				++benchmark;
			cur = cur->_left;
		}
		return CheckColor(root, 0, benchmark);
	}
private:
	Node* _root = nullptr;
};

模拟实现map:

#pragma once
#include"RBTree.h"
namespace ppr
{
	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;
		typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;

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

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

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

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

模拟实现set:

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

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

		pair<iterator, bool> Insert(const K& kv)
		{
			//类模版里面取内置类型要加上typename告诉编译器这是一个类型
			pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(kv);
			return pair<iterator, bool>(ret.first, ret.second);
		}
	private:
		RBTree<K, K, SetKeyOfT> _t;
	};
}

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

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

相关文章

无刷直流电机工作原理:【图文讲解】

电动机 (俗称马达) 是机械能与电能之间转换装置的通称。可以分为电动机和发电机.一般称电机时就是指电动机。这个在日常应用中&#xff0c;比较多见&#xff0c;比如机器人&#xff0c;手机&#xff0c;电动车等。 直流电机&#xff1a;分为有刷直流电机&#xff08;BDC&#…

本地ubuntu主机搭建我的世界服务器并免费开启公网映射 结合MESM面板 chmlfrp 保姆级教学

本地ubuntu主机搭建我的世界forge服务器并免费开启公网映射 结合MESM面板 chmlfrp 这是一篇很完成的从ssh命令->配置java环境->安装MCS->部署服务器->开启公网映射的我的世界保姆级开服教程,可以慢慢食用ଘ(੭ˊ꒳ˋ)੭ 。 为什么选择forge服务器进行开服&#x…

【前车之鉴】坑啊~ RestHighLevelClient 超时时间偶尔失效问题解决方案

文章目录 show me code缘起原因分析 几点建议 结论&#xff1a;实际你的配置是生效的&#xff0c;只不过效果不明显而已&#xff0c;通过下面的配置放大直观效果。 show me code 核心代码 public static void main(String[] args) {RestClientBuilder builder RestClient.bu…

【M2TR】M2TR: Multi-modal Multi-scale Transformers for Deepfake Detection

文章目录 M2TR: Multi-modal Multi-scale Transformers for Deepfake Detectionkey points研究贡献方法多尺度变压器频率过滤器跨模态融合损失函数SR-DF数据集实验总结M2TR: Multi-modal Multi-scale Transformers for Deepfake Detection 会议/期刊:ICMR ’22 作者: key …

深入理解栈(Stack)(纯小白进)

目录&#xff1a; 一、栈是什么&#xff1f;1. 栈的概念2.栈的结构选择 二、栈的实现1. 栈结构体的定义2. 栈的初始化3. 栈的销毁4. 入栈5.出栈6. 取栈顶元素7. 栈中元素的个数8. 判断栈是否为空 总结 一、栈是什么&#xff1f; 1. 栈的概念 栈&#xff08;Stack&#xff09;…

游戏开发指南:使用 UOS C# 云函数快速构建与部署服务端逻辑实战教学

零基础的服务端小白&#xff0c;现在也可以使用 Unity 结合 C# 来轻松搞定游戏服务端啦&#xff01; 在本篇文章中&#xff0c;我们将以游戏中的“抽卡”功能为例&#xff0c;展示如何使用 Unity Online Services&#xff08;UOS&#xff09;提供的强大 C# 云函数服务&#xf…

Elasticsearch(二)集成Spring Boot 基本的API操作

目录 一、集成Spring Boot 1、创建项目 2、pom文件 查看springboot集成的依赖 3、增加es的config类 二、索引相关API 1、创建索引 2、获取索引&#xff0c;判断其是否存在 3、删除索引 三、文档相关API 1、添加文档 2、获取文档&#xff0c;判断是否存在 3、获取文档…

Java后端面试----某团一面

美团一面 1.介绍一下你的第一个项目 这个就不多说了&#xff0c;主要是根据自己的简历上面的项目进行一个简短的概括使用的技术栈和什么背景解决了什么问题等等。 2.线程安全的类有哪些&#xff0c;平时有使用过哪些&#xff0c;主要解决什么问题 在Java中线程安全的类比如…

对后端返回的日期属性进行格式化(扩展 Spring MVC 的消息转换器)

格式化之前 格式化之后&#xff1a; 解决方式 方式一 在属性中加上注解&#xff0c;对日期进行格式化 JsonFormat(pattern "yyyy-MM-dd HH:mm:ss")private LocalDateTime createTime;//JsonFormat(pattern &quo…

echarts按需引入解决项目大小问题

背景&#xff1a; 按需加载缩减项目大小&#xff0c;提升项目性能和可用性 实现&#xff1a; 创建echarts.js main.js进行配置 页面中引用 效果 全量导入 按需加载&#xff1a;

Chrome清除nslookup解析记录 - 强制http访问 - 如何禁止chrome 强制跳转https

步骤&#xff1a; 地址栏输入 chrome://net-internals/#hsts在Delete domain 栏的输入框中输入要http访问的域名&#xff0c;然后点击“delete”按钮最后在Query domain 栏中搜索刚才输入的域名&#xff0c;点击“query”按钮后如果提示“Not found”即可&#xff01; 办法来自…

Linux系统:apt upgrade与apt update 命令的作用

一.sudo apt update命令 sudo apt update命令的主要作用是更新本地软件包列表。‌ 它不会下载或安装新的软件包&#xff0c;而是更新本地系统中软件包的列表&#xff0c;以反映远程存储库中的最新可用软件包信息。这确保了软件包管理器&#xff08;APT&#xff09;具有最新的软…

第十六周周报:单发的目标检测系列

目录 摘要 Abstract 一、SSD 1.1 模型结构 1.2 代码 二、YOLO 三、Termius 总结 摘要 本周主要学习单阶段的目标检测算法&#xff0c;如SSD、YOLO模型。详细学习了每个模型的原理&#xff0c;以及SSD和YOLO模型之间的异同。在本篇博客中将展示SSD的PyTorch实现代码&am…

Django使用uwsgi和nginx进行手动部署

在Django项目中使用uWSGI和Nginx进行部署是一种常见的生产环境配置。以下是一个详细的步骤指南&#xff0c;帮助你完成这个过程。 前提条件 有一个已经开发好的Django项目。服务器已安装Python、pip、Nginx和uWSGI。有一个有效的域名(可选&#xff0c;但推荐)。 步骤一&#xf…

CPU指令融合技术概述

什么是指令融合&#xff1f; 某些指令&#xff0c;例如add $3,$2,0, 只会使用rd/rs两个字段&#xff0c;但是这条指令却占用了全部32个bit, 这样会使得代码密度不高&#xff0c;指令域的有效利用率不高&#xff1b;这样&#xff0c;在实现某些功能的情况下&#xff0c;会使得CP…

Java创建线程池和线程池的七个核心参数

线程池的工作流程是&#xff1a;当一个任务被提交到线程池时&#xff0c;线程池会根据当前的线程数量和工作队列的状态来决定如何处理这个任务。如果当前运行的线程数量小于corePoolSize&#xff0c;则创建新线程执行任务&#xff1b;如果大于等于corePoolSize&#xff0c;则将…

毕设开源 大数据电影数据分析与可视化系统(源码+论文)

文章目录 0 前言1 项目运行效果2 设计概要3 最后 0 前言 &#x1f525;这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&#xff0c;往往达不到毕业答辩的要求&#xff0c;这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师…

完全免费安卓远程安卓方案:FRP+ADB甲壳虫方案,远程手机不是问题。

引言 在当今这个数字化时代&#xff0c;无论是在个人项目还是商业应用中&#xff0c;能够从公网访问到内网设备的能力变得越来越重要&#xff0c;尤其是安卓终端设备&#xff0c;在必要的情况下&#xff0c;从安卓远程到安卓进行紧急指导救援是未来一种重要的趋势. 通过合理的…

Java - WebSocket

一、WebSocket 1.1、WebSocket概念 WebSocket是一种协议&#xff0c;用于在Web应用程序和服务器之间建立实时、双向的通信连接。它通过一个单一的TCP连接提供了持久化连接&#xff0c;这使得Web应用程序可以更加实时地传递数据。WebSocket协议最初由W3C开发&#xff0c;并于2…

3种常用的缓存读写策略详解

在详解3种常用的缓存读写之前&#xff0c;我们先要了解什么事缓存读写。 缓存读写是指在使用缓存技术时&#xff0c;对数据进行读取和更新的操作过程。缓存是一种用于提高系统性能和可扩展性的技术&#xff0c;通过减少对慢速存储&#xff08;如数据库&#xff09;的访问次数&…