C++:用红黑树封装map与set-2

news2024/11/29 15:12:55

在这里插入图片描述

文章目录

  • 前言
  • 一、红黑树封装map与set中const迭代器
    • 1. 框架的搭建
    • 2. set实现const迭代器
    • 3. map实现const迭代器
  • 二、operator[ ]
    • 1. operator[ ]要达成的样子
    • 2. insert的改变
  • 三. 解决insert里set中的问题
  • 四. 解决map中的operator[ ]
  • 总结用红黑树封装map与set代码


前言

前面我们map与set封装的已经差不多了,接下来还有一些细节需要处理,本片博客主要解决const迭代器以及引发的一些其他问题~😘😘

总后的总结完整的源码封装的源码附上~🥰🥰

想看前面的封装是怎么实现的戳这里哦宝宝~❤️❤️
<( ̄︶ ̄)↗[GO!]


一、红黑树封装map与set中const迭代器

1. 框架的搭建

首先,和以前一样,要写出const迭代器,和以前一样通过控制三个模板参数,从而控制operator*以及operator->的返回值,从而控制普通迭代器与const迭代器。

在这里插入图片描述

改变模板参数,控制返回值:
在这里插入图片描述


2. set实现const迭代器

set的data中储存的是key,而且set是key的模型,我们说key是不可以被改变的,无论你是普通迭代器还是const迭代器,key都不可以被改变。

因此,这里set普通迭代器与set const迭代器都是RBTree::const迭代器,就达到了这样的效果:
在这里插入图片描述

tips:注意这里要加typename,模板类内的东西要在外部使用要告诉编译器哦

因此,我们在封装的时候要这样写:
在这里插入图片描述
这里的iterator是什么呢?是普通的迭代器吗?

答:不是的,这里我们typedef了,这里其实是红黑树中的const_iterator


3. map实现const迭代器

map与set不同,对于set来说,data中存储的是key,因此直接让它的普通迭代器与const迭代器都是const迭代器。

但是!对于map来说就不一样了,map的data中存储的是
pair<key, calue>,对于pair.first是不可以修改的,对于pair.second是需要修改的,如果直接将普通迭代器与const迭代器都是const迭代器,那么pair.first与pair.second都不能修改了。

因此我们需要想出其他的方法!

这里的解决方法是,将map的pair<key, calue>变为pair<const key, calue>,从一开始就限制key是不能够修改的,因此迭代器正常走就可以了~

在这里插入图片描述


二、operator[ ]

1. operator[ ]要达成的样子

对于map来说,因为map是key-value,因此我们要存在operator[ ],

  1. 通过[ ]进行插入
  2. 通过[ ]通过key改变value

因此我们实现operator[ ]是一定要借助insert的。
这里我们前面详细分析过,具体的分析过程戳这里哦

○( ^皿^)っHiahiahia…


2. insert的改变

通过前面的分析我们知道了,为了实现上述的目的,insert的返回值需要变成一个pair

在这里插入图片描述

因此:
在这里插入图片描述
当然,RBTree.h中的insert改了,对应的map与set也要相应的更改:

以下是我们的测试代码:

jyf::map<int, int> m;
m.insert(make_pair(1, 1));
m.insert(make_pair(3, 3));
m.insert(make_pair(2, 2));

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

	cout << mit->first << ":" << mit->second << endl;
	++mit;
}
cout << endl;

//func(m);

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

jyf::set<int> s;
s.insert(5);
s.insert(2);
s.insert(2);
s.insert(12);
s.insert(22);
s.insert(332);
s.insert(7);
jyf::set<int>::iterator it = s.begin();
while (it != s.end())
{
	// 不应该允许修改key
/*	if (*it % 2 == 0)
	{
		*it += 10;
	}*/

	cout << *it << " ";
	++it;
}
cout << endl;

但是,但是中的但是,就算我们改完了之后还是编译不通过:
在这里插入图片描述

我们看到这里,map好像没问题了,唯一出问题的就是set,因此我们要解决这个问题。


三. 解决insert里set中的问题

那么set为什么会出问题呢?
问题出在set中普通迭代器与const迭代器都是const迭代器!

如下图:
在这里插入图片描述

那这里怎么解决呢?

首先我们来套一层:

pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret来接收Insert的返回值,因此在ret中,pair的iterator是普通的iterator。

然后又用ret.first 与 ret.second来构造pair返回,也就是说pair是不能直接构造的时候这里first用const迭代器来构造,因此需要套一层,最后用普通迭代器构造cosnt迭代器。

pair<iterator, bool> insert(const K& key)
{
	pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);
	return pair<iterator, bool>(ret.first, ret.second);
}

但是只是这样还是编译不通过,还是老问题~

这是因为我们没有提供用普通迭代器构造const迭代器的构造函数:
解决方案如下:

在这里插入图片描述
这样我们的编译就可以通过了:
在这里插入图片描述


四. 解决map中的operator[ ]

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

以下是测试代码:

jyf::map<string, string> dict;
dict.insert(make_pair("sort", "xxx"));
dict["left"]; // 插入

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

dict["left"] = "左边"; // 修改
dict["sort"] = "排序"; // 修改
dict["right"] = "右边"; // 插入+修改

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

运行结果为:
在这里插入图片描述


总结用红黑树封装map与set代码

RBTree.h

#pragma once

#include<iostream>

using namespace std;



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)
		, _col(RED)
		, _data(data)
	{}
};

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

	// 解决insert中set的问题
	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)
	{
		return _node != s._node;
	}

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

	Self& operator++ ()
	{
		if (_node->_right != nullptr)
		{
			Node* curleft = _node->_right;
			while (curleft->_left)
			{
				curleft = curleft->_left;
			}
			_node = curleft;
		}
		else
		{
			// 找孩子是父亲左的那个祖先节点,就是下一个要访问的节点
			Node* cur = _node;
			Node* parent = _node->_parent;

			while (parent)
			{
				if (parent->_left == cur)
				{
					break;
				}
				else
				{
					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 && cur == parent->_left)
			{
				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* cur = _root;
		Node* parent = nullptr;

		KeyOfT kot;

		while (cur)
		{
			// 小于往左走
			if (kot(data) < kot(cur->_data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kot(data) > kot(cur->_data))   // 大于往右走
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return make_pair(iterator(cur), false);
			}
		}

		cur = new Node(data);
		// 其他结点初始颜色为红色
		cur->_col = RED;

		// 记录cur用于改变颜色后还能找到cur来返回
		Node* newnode = cur;

		// 链接
		if (kot(cur->_data) < kot(parent->_data))
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		// 如果我们parent是黑色,不用处理,就结束了

		// 情况一:cur为红,parent为红,grandfather为黑,uncle不确定
		// 用while循环是因为我们要不断的向上调整
		while (parent && parent->_col == RED)
		{
			// 首先我们要找到grandfather
			Node* grandfather = parent->_parent;

			// 接下来通过grandfather找到uncle

			//如果parent是grandfather->_left
			if (parent == grandfather->_left)
			{
				// 说明uncle在右边
				Node* uncle = grandfather->_right;

				// uncle存在且为红
				if (uncle && uncle->_col == RED)
				{
					// 满足上述情况,开始调整颜色
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续向上调整
					cur = grandfather;
					parent = cur->_parent;

					// 但是走到这里有一个问题,如果当前parent就是根,
					// 并且parent是红色,我们要把它变为黑色,
					// 处理方式是:出循环后,暴力将_root->_col变为黑色
				}
				else    // uncle不存在 或 uncle存在且为黑色
				{
					// 判断要怎样旋转

					// 右单旋
					if (cur == parent->_left)
					{
						//     g
						//   p
						// c
						RotateR(grandfather);
						// 调整颜色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else // 左右双旋
					{
						//     g
						//   p
						//		c
						RotateL(parent);
						RotateR(grandfather);
						// 调整颜色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					// 旋转变色完就结束了,这里不加这个也可以,条件判断就会退出
					break;
				}
			}
			else //(parent == grandfather->_right)  // 如果parent是grandfather->_right
			{
				// 说明uncle在左边
				Node* uncle = grandfather->_left;

				// uncle存在且为红
				if (uncle && uncle->_col == RED)
				{
					// 满足上述情况,开始调整颜色
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续向上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else    // uncle不存在 或 uncle存在且为黑色
				{
					// 判断要怎样旋转

					// 左单旋
					if (cur == parent->_right)
					{
						// g
						//	  p
						//       c
						RotateL(grandfather);
						// 调整颜色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else // 右左双旋
					{
						// g
						//	  p
						// c
						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存在
		{
			curleft->_parent = parent;
		}

		cur->_left = parent;

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

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

			cur->_parent = ppnode;
		}
	}

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

		parent->_left = curright;

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

		cur->_right = parent;

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

	// 检查是否构建正确
	bool CheckColour(Node* root, int blacknum, int benchmark)
	{
		if (root == nullptr)
		{
			if (blacknum != benchmark)
				return false;

			return true;
		}

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

		if (root->_col == RED && root->_parent && root->_parent->_col == RED)
		{
			cout << kot(root->data) << "出现连续红色节点" << endl;
			return false;
		}

		return CheckColour(root->_left, blacknum, benchmark)
			&& CheckColour(root->_right, blacknum, benchmark);
	}

	bool IsBalance()
	{
		return IsBalance(_root);
	}

	bool IsBalance(Node* root)
	{
		if (root == nullptr)
			return true;

		if (root->_col != BLACK)
		{
			return false;
		}

		// 基准值
		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				++benchmark;

			cur = cur->_left;
		}

		return CheckColour(root, 0, benchmark);
	}

private:
	Node* _root = nullptr;
};

myset.h

#pragma once
#include"RBTree.h"

namespace jyf
{
	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& key)
		{
			pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);
			return pair<iterator, bool>(ret.first, ret.second);
		}

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

mymap.h

#pragma once

#include"RBTree.h"

namespace jyf
{
	template<class K, class V>
	class map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<const 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;
	};
}

test.cpp

#include<iostream>
#include<string>
using namespace std;

#include"mymap.h"
#include"myset.h"

//void func(const jyf::map<int, int>& m)
//{
//	//auto mit = m.begin();
//	jyf::map<int, int>::const_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;
//}

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

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

		cout << mit->first << ":" << mit->second << endl;
		++mit;
	}
	cout << endl;

	//func(m);

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

	jyf::set<int> s;
	s.insert(5);
	s.insert(2);
	s.insert(2);
	s.insert(12);
	s.insert(22);
	s.insert(332);
	s.insert(7);
	jyf::set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		// 不应该允许修改key
	/*	if (*it % 2 == 0)
		{
			*it += 10;
		}*/

		cout << *it << " ";
		++it;
	}
	cout << endl;

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

	jyf::map<string, string> dict;
	dict.insert(make_pair("sort", "xxx"));
	dict["left"]; // 插入

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

	dict["left"] = "左边"; // 修改
	dict["sort"] = "排序"; // 修改
	dict["right"] = "右边"; // 插入+修改

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


	return 0;
}

🥰🥰🥰到这里就结束啦,创作不易,如果感觉对您有帮助的话,求求一个一键三连,谢谢各位大佬~🥰🥰🥰

在这里插入图片描述

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

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

相关文章

微信小程序下拉刷新与上拉触底的全面教程

微信小程序下拉刷新与上拉触底的全面教程 引言 在微信小程序的开发中,用户体验至关重要。下拉刷新和上拉触底是提高用户交互体验的重要功能,能够让用户轻松获取最新数据和内容。本文将详细介绍这两个功能的实现方式,结合实际案例、代码示例和图片展示,帮助开发者轻松掌握…

【博主推荐】C#中winfrom开发常用技术点收集

文章目录 前言1.打开文件夹并选中文件2.窗体之间传参3.异步调用&#xff1a;让数据处理不影响页面操作4.创建一个多文档界面(MDI) 应用程序5.在WinForms中使用数据绑定6.在WinForms中后台使用控件的事件处理7.在WinForms中窗体跳转的几种方式8.后台处理方法中&#xff0c;调用窗…

第四十二篇 EfficientNet:重新思考卷积神经网络的模型缩放

文章目录 摘要1、简介2、相关工作3、复合模型缩放3.1、 问题公式化3.2、扩展维度3.3、复合比例 4、EfficientNet架构5、实验5.1、扩展MobileNets和ResNets5.2、EfficientNet的ImageNet结果5.3、EfficientNet的迁移学习结果 6、讨论7、结论 摘要 卷积神经网络(ConvNets)通常在固…

【Android】MMKV—高性能轻量化存储组件

【Android】MMKV—高性能轻量化存储组件 本文参考以及学习文档&#xff1a; Android存储&#xff1a;轻松掌握MMKV通过学习本文&#xff0c;轻松掌握腾讯开发的 MMKV 组件&#xff0c;尽早在项目中替换掉SharedPr - 掘金 MMKV——Android上的使用(替换SP存储)MMKV 是基于 mmap …

python+django自动化平台(一键执行sql) 前端vue-element展示

一、开发环境搭建和配置 pip install mysql-connector-pythonpip install PyMySQL二、django模块目录 dbOperations ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-313.pyc │ ├── admin.cpython-313.pyc │ ├── apps.cpython-313.pyc │ …

arm Rk1126 编译Qt工程报错: Could not find qmake spec

首先修改qmake.conf文件&#xff0c;配置好正确的交叉编译工具&#xff1a; 然后执行编译&#xff1a; /opt/Rv1126/Rv1126-盒子代码/rv1126-qt5-sdk/bin/qmake untitled.pro 报错。 原因&#xff1a;中文路径。修改路径为英文路径即可

[保姆式教程]使用labelimg2软件标注定向目标检测数据和格式转换

定向目标检测是一种在图像或视频中识别和定位对象的同时&#xff0c;还估计它们方向的技术。这种技术特别适用于处理有一定旋转或方向变化的对象&#xff0c;例如汽车、飞机或文本。定向目标检测器的输出是一组旋转的边界框&#xff0c;这些框精确地包围了图像中的对象&#xf…

C语言刷题笔记3(7)

7.1 数组处理斐波那契数列 题目描述:用数组来处理Fibonacci数列并输出。 输入:一个不超过40且大于2的整数n&#xff0c;表示需要处理并输出的Fibonacci数个数。 输出:输出前n个Fibonacci数&#xff0c;每行输出5个值&#xff0c;按每12位向右对齐的方式输出。请注意不要在第…

PHP 去掉特殊不可见字符 “\u200e“

描述 最近在排查网站业务时&#xff0c;发现有数据匹配失败的情况 肉眼上完全看不出问题所在 当把字符串 【M24308/23-14F‎】复制出来发现 末尾有个不可见的字符 使用删除键或左右移动时才会发现 最后测试通过 var_dump 打印 发现这个"空字符"占了三个长度 &#xf…

构建 LLM (大型语言模型)应用程序——从入门到精通(第七部分:开源 RAG)

通过检索增强生成 (RAG) 应用程序的视角学习大型语言模型 (LLM)。 本系列博文 简介数据准备句子转换器矢量数据库搜索与检索大语言模型开源 RAG&#xff08;本帖&#xff09;评估服务LLM高级 RAG 1. 简介 我们之前的博客文章广泛探讨了大型语言模型 (LLM)&#xff0c;涵盖了其…

linux基础2

声明&#xff01; 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&#…

智能产品综合开发 - 手势识别

1 实训选题目的 本次实训选择的题目是“基于树莓派的手势识别系统”&#xff0c;旨在为人们提供一种便捷的交互方式&#xff0c;使用户能够通过手势控制智能设备&#xff0c;摆脱传统的物理按键操作。通过本项目&#xff0c;我们希望能实现快速、灵活的手势识别&#xff0c;提升…

Qt常用控件之显示类控件

目录 QLabel 文本格式 设置图片 文本对齐/自动换行/边距/缩进 设置伙伴 QLCDNumber 倒计时功能 QProgressBar 进度条 QCalendarWidget QLabel QLabel 同样是 QWidget 的子类&#xff0c;所以前面博客中 QWidget 中的属性方法也是适用的 QLabel可以用来显示文本和图…

架构-微服务-环境搭建

文章目录 前言一、案例准备1. 技术选型2. 模块设计3. 微服务调用 二、创建父工程三、创建基础模块四、创建用户微服务五、创建商品微服务六、创建订单微服务 前言 ‌微服务环境搭建‌ 使用的电商项目中的商品、订单、用户为案例进行讲解。 一、案例准备 1. 技术选型 maven&a…

【JTAG】1149.6协议总结

【JTAG】1149.6协议详解-CSDN博客 IEEE 1149.6标准的基本实现需要在信号路径驱动器中添加一个时脉产生器&#xff0c;它能发射单一脉冲或一列脉冲&#xff0c;这取决于被加载到 1149.1 指令暂存器中的 EXTEST_PULSE 或 EXTEST_TRAIN 指令。1149.6在克服信道中共模讯号干扰能力…

小程序 - 个人简历

为了让招聘人员快速地认识自己&#xff0c;可以做一个“个人简历”微信小程序&#xff0c; 展示自己的个人信息。 下面将对“个人简历”微信小程序进行详细讲解。 目录 个人简历 创建图片目录 页面开发 index.wxml index.wxss 功能实现截图 总结 个人简历 创建图片目录…

Tülu 3:重新定义开源大模型的后训练范式

一、引言 在大型语言模型&#xff08;LLM&#xff09;的发展历程中&#xff0c;预训练阶段往往受到最多关注&#xff0c;动辄需要数百万美元算力投入和数万亿token的训练数据。然而&#xff0c;一个鲜为人知但同样关键的事实是&#xff1a;预训练完成的模型实际上并不能直接投…

systemverilog约束中:=和:/的区别

“x dist { [100:102] : 1, 200 : 2, 300 : 5}” 意味着其值等于100或101或102或200或300其中之一&#xff0c; 其权重比例为1:1:1:2:5 “x dist { [100:102] :/ 1, 200 : 2, 300 : 5}” 意味着等于100&#xff0c;101&#xff0c;102或200&#xff0c;或300其…

用Pycharm安装manim

由于版本和工具的差异&#xff0c;manim的安装方式不尽相同。本文用Pycharm来安装manim. 一、准备工作&#xff1a;安装相应版本的python、pycharm和ffmpeg. 此处提供一种安装ffmpeg的方式 下载地址&#xff1a;FFmpeg 下载后&#xff0c;解压到指定目录。 配置环境变量&am…

云GPU——pycharm远程连接featurize实例

点击PyCharm远程连接会有详细的教程&#xff0c; 本文补充虚拟环境的创建以及包的下载。 1、虚拟环境的创建&#xff1a; 2、虚拟环境创建好之后&#xff0c;下载需要的包 &#xff08;这种方法比较快&#xff09; 可以在python interpreter点击go to tool window&#xff0c…