用红黑树封装出map与set

news2024/9/20 20:44:38

目录

一、红黑树的改造

节点结构的定义

 迭代器类的实现

红黑树中提供迭代器

红黑树的主要代码

二、set的实现

三、map的实现

四、测试代码


map与set的底层都是红黑树,所以本篇文章就分享如何用同一颗红黑树封装出map与set

所以大家可以先去看一下我的讲解红黑树的博客:实现红黑树-CSDN博客

一、红黑树的改造

节点结构的定义

//节点结构定义
template<class T>
struct RBTreeNode
{
	//三叉链
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;

    T _data; //由于要用红黑树封装出map与set, 因此不知道_data是什么类型,有可能是单纯的值,也有可能是pair键值对!

	//颜色变量
	Color _col;

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

 迭代器类的实现

与模拟实现list一样,由于迭代器的统一操作是it++, 而在链表/树中,直接++是无法到下一个节点的,因此需要把节点指针封装成一个类,从而提供迭代器

//map/set的迭代器也是对节点指针封装成的类
template<class T, class Ref, class Ptr>
struct __TreeIterator
{
	typedef RBTreeNode<T> Node;
	Node* _node;

	typedef __TreeIterator<T, Ref, Ptr> self;

	//构造函数
	__TreeIterator(Node* node)
		:_node(node)
	{}
	
	//*it
	Ref operator*()
	{
		return _node->_data;
	}

	//it->
	Ptr operator->()
	{
		return &_node->_data;
	}

	//求二叉树中序遍历过程中的下一个节点(左子树,根,右子树)
	//1.当前节点的右子树存在,下一个节点就是右子树的最左节点
	//2.当前节点的右子树不存在,说明以当前节点为根的树已经访问完毕,向上找到孩子是父亲左的那个祖先
	self& operator++()
	{
		if (_node->_right)
		{
			Node* cur = _node->_right;
			while (cur->_left)
			{
				cur = cur->_left;
			}
			_node = cur;
		}
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_right) //也有可能找到了根,此时也就结束了!
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}

	//求二叉树中序遍历过程中的上一个节点(左子树,根,右子树)
	//1.it的左子树存在,找左子树的最右节点
	//2.it的左子树不存在, 向上找孩子是父亲右的那个祖先
	self& operator--()
	{
		if(_node->_left)
		{
			Node* cur = _node->_left;
			while (cur)
			{
				cur = cur->_right;
			}
			_node = cur;
		}
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}

	//迭代器的比较本质是节点指针的比较
	bool operator==(const self& s)
	{
		return _node == s._node;
	}

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

上述迭代器的实现很多与list模拟实现是一样的: list使用与模拟实现-CSDN博客

关键的地方在于迭代器++与迭代器--如何实现,由于map/set的遍历是中序遍历,因此迭代器++的本质就是要找二叉树中序遍历过程中当前节点的下一个节点, 而中序遍历的顺序是: 左子树 根 右子树, 因此如果当前树的右子树存在,那么下一个节点就是右子树的最左节点;

如果右子树不存在,说明以当前节点为根的树已经访问完了,如果当前节点的父亲依旧是右孩子,说明以当前节点父亲为根的树也访问完了,依次类推~因此我们需要向上找孩子是父亲左的那个祖先,  才是下一个要访问的节点

找中序遍历的上一个节点也是同样的道理, 此处就不赘述了~

红黑树中提供迭代器

template<class K, class T, class KeyOfT>
class RBTree
{
public:
	typedef RBTreeNode<T> Node;
	typedef __TreeIterator<T, T&, T*> iterator; //普通迭代器的定义
	typedef __TreeIterator<T, const T&, const T*> const_iterator; //const迭代器的定义
public:

	//begin()返回中序遍历的起始节点指针构造的迭代器
	//起始位置是左子树的最左节点
	iterator begin()
	{
		Node* cur = _root; //_root可能为空,因此下面while加了cur不为空的判断
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return iterator(cur);
	}

	const_iterator begin() const
	{
		Node* cur = _root; //_root可能为空,因此下面while加了cur不为空的判断
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return const_iterator(cur);
	}

	//end()返回空指针
	iterator end()
	{
		return iterator(nullptr);
	}

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

红黑树的主要代码

模板参数

1.需要三个模板参数,第二个模板参数就是 T, 实现set时,T就是key, 实现map时,T就是pair<key, value>;

2.插入节点时需要比较,如果是set, 就直接比较key, 如果是map, 比较的是pair中的key, 因此比较规则是不一样的,所以第三个模板参数要传仿函数, 在比大小之前先获取key

3.在find函数中,参数直接要用到key, 而pair中的key只能取到类型,因此第一个模板参数还需要把key传进来

除了仿函数的使用与insert的返回值,代码中的其他地方与 实现AVL树-CSDN博客 基本一样的

//必须要有第一个参数,因为find函数要根据key类型的参数去查找,而我们无法直接从T中拿到key类型
template<class K, class T, class KeyOfT> //第三个参数是仿函数,方便我们根据传递的类确定比较规则
class RBTree
{
public:
	typedef RBTreeNode<T> Node;
	typedef __TreeIterator<T, T&, T*> iterator; //迭代器的定义
	typedef __TreeIterator<T, const T&, const T*> const_iterator; //迭代器的定义
public:

	//迭代器
	//begin()返回中序遍历的起始节点指针构造的迭代器
	iterator begin()
	{
		Node* cur = _root; //_root可能为空,因此下面while加了cur不为空的判断
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return iterator(cur);
	}

	const_iterator begin() const
	{
		Node* cur = _root; //_root可能为空,因此下面while加了cur不为空的判断
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return const_iterator(cur);
	}

	//end()返回空指针
	iterator end()
	{
		return iterator(nullptr);
	}

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

    //pair<iterator, bool> Insert(const T& data) err, iterator无法转化为set中的const_iterator
	//set中的iterator本质是const_iterator, 本质是__TreeIterator<T, const T&, const T*>类型
	//而此处返回的iterator,类型是__TreeIterator<T, T&, T*>
	//这两种类型是完全不同的,因此会报错,而当我们使用Node*就可以解决问题了,因为pair类的构造函数/拷贝构造函数设计的很好~
	pair<Node*, bool> Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(_root, true);
		}

		Node* parent = nullptr;
		Node* cur = _root;
		KeyOfT kot;

		while (cur)
		{
			//库中pair的比较规则:
			//first小就小,first一样则second小就小
			//而我们期望比较规则: 只按照first比较!!!
			if (kot(cur->_data) < kot(data)) //如果data是pair类型,而库中pair类型比较规则不符合我们的期望, 因此要传仿函数指定比较规则
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kot(cur->_data) > kot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return make_pair(cur, false);
			}
		}
		cur = new Node(data);
		Node* newNode = cur;
		if (kot(parent->_data) < kot(data))
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		while (parent && parent->_col == RED) //父亲不存在或者父亲为黑色就不需要继续调整了!
		{
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent) //父亲是爷爷的左孩子
			{
				Node* uncle = grandfather->_right;
				//1.插入节点的叔叔存在且为红色 --- 父亲和叔叔变黑,爷爷变红
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//调整完之后,更新cur, 判断是否还需要调整
					cur = grandfather;
					parent = cur->_parent;
				}
				//2.uncle不存在 / uncle为黑色 --- 旋转 + 变色
				else
				{
					if (cur == parent->_left) //单纯左边高
					{
						RotateR(grandfather); //右单旋

						//变色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else //cur是parent的右边
					{
						RotateLR(grandfather); //左右双旋

						//变色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break; //旋转+变色完之后, 该子树的根变成了黑色,无需继续调整
				}
			}

			else //父亲是爷爷的右孩子
			{
				Node* uncle = grandfather->_left;
				//1.插入节点的叔叔存在且为红色 --- 父亲和叔叔变黑,爷爷变红
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//调整完之后,更新cur, 判断是否还需要调整
					cur = grandfather;
					parent = cur->_parent;
				}
				//2.uncle不存在 / uncle为黑色 --- 旋转 + 变色
				else
				{
					if (cur == parent->_left) //cur是parent的左边,进行右单旋
					{
						RotateRL(grandfather); //右左单旋

						//变色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					else //cur是parent的右边
					{
						RotateL(grandfather); //左单旋

						//变色
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					break; //旋转+变色完之后, 该子树的根变成了黑色,无需继续调整
				}
			}
		}

		_root->_col = BLACK;

		return make_pair(cur, true);
	}


	iterator find(const K& key)
	{
		Node* cur = _root;
		KeyOfT kot;
		if (key > kot(_root->_data))
		{
			cur = cur->_right;
		}
		else if (key < kot(_root->_data))
		{
			cur = cur->_left;
		}
		else
		{
			return iterator(cur);
		}
		return iterator(end());
	}

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

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

		Node* parentParent = parent->_parent;

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

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

			subR->_parent = parentParent;
		}
	}

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

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

		Node* parentParent = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

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

			subL->_parent = parentParent;
		}
	}

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

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

	//判断是否是红黑树
	bool IsRBTree()
	{
		if (_root == nullptr) return true;
		if (_root->_col == RED)
		{
			cout << "根节点为红色" << endl;
			return false;
		}

		//以最左路径的黑节点的个数作为参考值
		Node* cur = _root;
		int BlackCount = 0;
		while (cur)
		{
			if (cur->_col == BLACK)
				BlackCount++;
			cur = cur->_left;
		}

		//调用子函数判断红黑树
		int count = 0;
		return _IsRBTree(_root, count, BlackCount);
	}
private:
	bool _IsRBTree(Node* root, int count, int BlackCount)
	{
		if (root == nullptr)
		{
			if (count != BlackCount)
			{
				cout << "黑色节点的个数不相等" << endl;
				return false;
			}
			return true;
		}

		//判断节点的孩子节点颜色不好判断, 转化成判断父亲节点颜色
		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << "存在连续的红节点" << endl;
			return false;
		}

		if (root->_col == BLACK)
			count++;

		//递归子问题
		return _IsRBTree(root->_left, count, BlackCount)
			&& _IsRBTree(root->_right, count, BlackCount);
	}

private:
	Node* _root = nullptr;
};

二、set的实现

由于set中的值是不能被修改的,因此无论是set提供的普通迭代器还是const迭代器,都不能去修改元素的值,因此两类迭代器的底层都是红黑树的const迭代器

namespace dck
{
	template <class K>
	class set
	{
	public:
		struct SetKeyOfT
		{
			const K& operator()(const K& key) //()运算符重载
			{
				return key; //set传仿函数只是为了配合map!!!
			}
		};

		//非const迭代器和const迭代器本质都是const迭代器,这样的话无论用哪个set都不能被修改了!!
		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)
		{
			return _t.Insert(key);
		}

		iterator find(const K& key)
		{
			return _t.find(key);
		}
	private:
		//第二个模板参数决定了set中存储什么
		RBTree<K, K, SetKeyOfT> _t;
	};
}

三、map的实现

map中的元素是pair<key, value>, 但key不能修改,value可以修改, 另外,map中的[ ]重载是借助insert实现的,这点我们在讲set与map使用-CSDN博客使用时已经提到过

同时为了解决普通对象(非const对象)的pair类型的first不能修改,second可以修改,我们可以把pair类型的first 传成 const K

namespace dck
{
	template <class K, class V> 
	class map
	{
	public:
		struct MapKeyOfT
		{
			const K& operator()(const pair<const K, V>& kv) //()运算符重载
			{
				return kv.first; //map传仿函数就是为了获取kv中的key, 从而比较能够按照key比较
			}
		};

		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<const K, V>& kv)
		{
			return _t.Insert(kv);
		}

		iterator find(const K& key)
		{
			return _t.find(key);
		}

	private:
		//第二个模板参数决定了map中存储什么
		RBTree<K, pair<const K, V>, MapKeyOfT> _t;
	};
}

四、测试代码

#include "MySet.h"
#include "MyMap.h"
#include <set>

void test_MySet()
{
	dck::set<int> s1;
	s1.insert(6);
	s1.insert(2);
	s1.insert(3);
	s1.insert(4);
	dck::set<int>::iterator it1 = s1.begin();
	while (it1 != s1.end())
	{
		cout << *it1 << " ";
		++it1;
	}
	cout << endl;

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


void test_MyMap1()
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };
	dck::map<string, int> countMap;

	dck::map<string, int>::iterator it = countMap.begin();

	for (auto& e : arr)
	{
		countMap[e]++;
	}
	for (auto& e : countMap)
	{
		//e.first = "haha"; //不能修改
		//e.second = 2; //可以修改
		cout << e.first << ":" << e.second << endl;
	}
	cout << endl;
}

void test_MyMap2()
{
	dck::map<string, string> countMap;
	countMap.insert(make_pair("left", "左边"));
	countMap.insert(make_pair("right", "右边"));
	countMap.insert(make_pair("insert", "插入"));
	dck::map<string, string>::iterator it = countMap.find("left");
	if (it != countMap.end())
		it->second = "剩余";
	for (auto& e : countMap)
	{
		cout << e.first << ":" << e.second << endl;
	}
	cout << endl;
}


int main()
{
	test_MySet();
	//test_MyMap1();
	//test_MyMap2();
	return 0;
}

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

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

相关文章

先有JVM还是先有垃圾回收器?很多人弄混淆了

是先有垃圾回收器再有JVM呢&#xff0c;还是先有JVM再有垃圾回收器呢&#xff1f;或者是先有垃圾回收再有JVM呢&#xff1f;历史上还真是垃圾回收更早面世&#xff0c;垃圾回收最早起源于1960年诞生的LISP语言&#xff0c;Java只是支持垃圾回收的其中一种。下面我们就来刨析刨析…

ue引擎游戏开发笔记(38)——实现敌人接收攻击伤害,并作出反应

1.需求分析&#xff1a; 现在已经显示造成实际伤害&#xff0c;但敌人对实际伤害并未产生反馈&#xff0c;例如还击&#xff0c;或者死亡倒地等等&#xff0c;实现敌人对于受击的反馈。 2.操作实现&#xff1a; 1.思路&#xff1a;在动画蓝图中添加死亡动画&#xff0c;并通过…

wefaf

c语言中的小小白-CSDN博客c语言中的小小白关注算法,c,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm1001.2014.3001.5343 给大家分享一句我很喜欢我话&#xff1a; 知不足而奋进&#xff0c;望远山而前行&am…

Python 渗透测试:反弹 shell (反弹 后门 || 程序免杀)

什么叫 反弹 shell 反弹 shell (Reverse Shell) 是一种常见的渗透测试技术,它指的是受害者主机主动连接攻击者的主机,从而让攻击者获得对受害者主机的控制权。在一个典型的反弹 shell 攻击中,攻击者会在自己的主机上监听一个特定的端口,然后诱使目标主机主动连接到这个端口。当…

Cadence 16.6 绘制PCB封装时总是卡死的解决方法

Cadence 16.6 绘制PCB封装时总是卡死的解决方法 在用Cadence 16.6 PCB Editor绘制PCB封装时候&#xff0c;绘制一步卡死一步&#xff0c;不知道怎么回事儿&#xff0c;在咨询公司IT后&#xff0c;发现是WIN系统自带输入法的某些热键与PCB Editor有冲突&#xff0c;导致卡死。 …

网络地址转换(nat,easy ip,nat server)资源上传

实验概述 由内到外 nat&#xff0c;easy ip&#xff0c;转换的是源ip nat server 由外到内&#xff0c;转换的是目的IP 实验拓扑 结果验证 nat实验得到结果 1.ar1到ar3没有路由也可以访问 2.ar3配置telent后ar1也可以通过telnet远程配置 esay ip 如果ar2 g0/0/1接口ip非固…

Qwen 开源标杆

Qwen的博客 在线体验Qwen开源版本、非常丝滑 不算量化、已经开源的Qwen1.5 版本有9个&#xff1a; 0.5B、1.8B、4B、7B、14B、32B、72B、110B、MoE-A2.7B 闭源已经发展到 Qwen-Max-0428、网页端从2.1升级到2.5 Qwen API详情 一些记录&#xff1a; 1、Qwen1.5 110B&#x…

汇舟问卷:5年专业经验,海外渠道查无需烦恼!

大家好&#xff0c;我是汇舟问卷&#xff0c;拥有五年的行业经验&#xff0c;专注于海外问卷渠道查。 在海外问卷渠道查领域&#xff0c;我们拥有专业的知识和经验。无需为购买大量海外邮箱而烦恼&#xff0c;更无需担忧账号被封禁的风险。我们提供全天候24小时的服务&#xf…

通过视频生成实现基于物理的3D对象交互——PhysDreamer

随着虚拟现实(VR)和增强现实(AR)技术的飞速发展&#xff0c;用户对于虚拟体验的真实性提出了更高的要求。在这样的背景下&#xff0c;PhysDreamer应运而生&#xff0c;它是一项创新的技术&#xff0c;能够为静态3D对象赋予逼真的物理交互动态&#xff0c;极大地丰富了虚拟环境的…

Windows内核函数 - ASCII字符串和宽字符串

本章介绍了Windows内核中字符串处理函数、文件读写函数、注册表读写函数。这些函数是DDK提供的运行时函数&#xff0c;他们比标准C语言的运行时函数功能更丰富。普通的C语言运行时库是不能在内核模式下使用的&#xff0c;必须使用DDK提供的运行时函数。 和应用程序一样&#xf…

四川景源畅信:如何更好的为抖音小店做引流?

在数字化营销的浪潮中&#xff0c;抖音小店作为新兴的电商形态&#xff0c;正以其独特的社交属性和流量优势吸引着众多商家的目光。如何为抖音小店引流&#xff0c;成为许多店主心中的疑问。本文将深入探讨有效提升店铺流量的策略&#xff0c;助你在抖音平台上快速崛起。 一、内…

云飞云共享云桌面如何降低电脑投入成本?

云飞云共享云桌面作为一种创新的云计算解决方案&#xff0c;以其独特的优势在业界赢得了众多认可。其中&#xff0c;它极大地降低了电脑投入成本&#xff0c;为企业和个人用户带来了实实在在的经济效益。那么&#xff0c;云飞云共享云桌面是如何实现这一点的呢&#xff1f; 设…

pytest教程-46-钩子函数-pytest_sessionstart

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 上一小节我们学习了pytest_report_testitemFinished钩子函数的使用方法&#xff0c;本小节我们讲解一下pytest_sessionstart钩子函数的使用方法。 pytest_sessionstart 是 Pytest 提供的一个钩子函数&#xff0c…

MaxKB创建本地知识库

上节已经可以通过MaxKB创建简单的问答系统了&#xff0c;这节开始做自己的知识库&#xff0c;实际上就是把一些本地文件上传到大模型中&#xff0c;让大模型学会这些文件内容&#xff0c;你在问他问题的时候可以通过此文件的内容来回答你&#xff0c;尤其是在针对特定场景或者特…

第9章.Keil5-MDK软件简介

目录 0. 《STM32单片机自学教程》专栏 9.1 主界面 9.2 文本格式编辑 9.3 代码提示&语法检测&代码模版 9.4 其他小技巧 9.4.1 TAB 键的妙用 9.4.2 快速定位函数/变量被定义的地方 9.4.3 快速注释与快速消注释 9.4.4 快速打开头文件 9.4.5 查找替换…

C++基础——继承(下)

一、继承与静态成员 基类定义了static 静态成员&#xff0c;则整个继承体系里面只有一个这样的成员。无论派生出多少个子 类&#xff0c;都只有一个 static 成员实例 。 class person { public:person(const char* name "lisi"):_name(name){} public:string _name;…

【网站项目】SpringBoot796水产养殖系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

结合多模态 AI 谷歌展示 AR 眼镜原型机;Meta 被曝开发带摄像头的 AI 耳机丨 RTE 开发者日报 Vol.204

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」&#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE&#xff08;Real Time Engagement&#xff09; 领域内「有话题的新闻」、「有态度的观点」、「有意思的数据」、「有思考的文章」、「…

数据可视化的艺术:使用Matplotlib和Seaborn揭示数据故事

引言 数据可视化是数据分析中的关键一环&#xff0c;它帮助我们理解数据模式、趋势和异常。在Python中&#xff0c;Matplotlib和Seaborn是两个流行的数据可视化库&#xff0c;它们提供了丰富的图表和图形选项&#xff0c;使数据的可视化变得简单而强大。 Matplotlib&#xff…

16.ABA问题

文章目录 ABA问题1.什么是ABA问题&#xff1f;2.ABA问题解决方案2.1.使用AtomicStampedReference解决ABA问题2.2.使用AtomicMarkableReference解决ABA问题 ABA问题 因为CAS操作的原子性能高&#xff0c;在JUC中广泛被应用&#xff0c;但是如果使用的不合理&#xff0c;CAS操作就…