使用红黑树模拟实现map和set

news2024/11/23 23:45:35

在STL的源代码中,map和set的底层原理都是红黑树。但这颗红黑树跟我们单独写的红黑树不一样,它需要改造一下:

改造红黑树

节点的定义

因为map和set的底层都是红黑树。而且map是拥有键值对pair<K,V>的,而set是没有键值对,只有一个K。因此,为了应对这两种不同的情况,就使用模板参数T。

当map使用这棵红黑树的时候,T就会变成pair<K,V>。当set使用时,T就会变成K。

//使用枚举
enum Colour
{
	RED,
	BLACK,
};


// 如果是map,则为pair<K, V>; 如果是set,则为k
//因此,对于节点来说,需要再套一层模板,来应付两种不同的情况
template<class T>
struct RBTreeNode
{
	T _data;
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	Colour _col;

	RBTreeNode(const T& data)
		:_data(data)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(RED)  //默认是红色
	{}
};

改版的红黑树的插入操作

红黑树的模板参数有三个:K、valueType和仿函数KeyOfValue。

K是key的类型,ValueTyp要么是set的K,要么是map的pair<const K,V>。

而仿函数是为了比较节点的值的大小的,因为官方库中键值对pair<K,V>比较大小的方法是比较first和second。但是红黑树中,新增节点是通过比较first,因此我们需要自己写一个仿函数用于单独比较first。

//跟节点的定义一样
//因为关联式容器中存储的是<key, value>的键值对,因此
// k为key的类型,
// ValueType: 如果是map,则为pair<K, V>; 如果是set,则为k。如下:
// map->RBTree<K,pair<const K,V>,MapKeyOfT> _t;
// set->RBTree<K,K,SetKeyOfT> _t
// 
// KeyOfValue: 通过value来获取key的一个仿函数类。其作用就是用于比较。
template<class K,class ValueType,class KeyOfValue>
class RBTree
{
	typedef RBTreeNode<ValueType> Node;
public: 
    //迭代器
    typedef __RBTreeIterator<ValueType> iterator;
   //......
private:
	Node* _root = nullptr;
};

迭代器

红黑树的迭代器不能使用原生指针,因为它是一棵树,节点地址不连续,因此需要封装起来。

//set的K,map的pair<K,V>
template<class ValueType>
struct __RBTreeIterator
{
	typedef RBTreeNode<ValueType> Node;
	typedef __RBTreeIterator<ValueType> Self;
	Node* _node;

	__RBTreeIterator(Node* node)
		:_node(node)
	{}
};

1.operator++()

红黑树是二叉搜索树,采用的是中序遍历的方法。因此,迭代器每次向前走,是按照中序遍历的顺序走的。

因此,对于往前++,有两种情况:第一种情况是当前节点的右孩子不为空,那么按照中序遍历的顺序,下一个节点便是右子树的最小值。第二种情况是当前节点的右孩子为空:这种情况下,又分两种情况:①当前节点是父节点的左孩子,那么下一个节点肯定是当前节点的父节点。②当前节点是父节点的右孩子,这意味着当前节点的父节点也被遍历了,又没有右孩子节点,只能往上找了,找的节点是祖先节点,而这个祖先节点是特征是:孩子节点是它的左节点。

 

	//左 根  右
	Self& operator++()
	{
		//当当前节点的右子树不为空,根据中序遍历的顺序,那就找右子树最小值
		if (_node->_right)
		{
			Node* minValue = _node->_right;
			while (minValue->_left)
			{
				minValue = minValue->_left;
			}

			//找到最小值的节点后,更新节点
			_node = minValue;
		}
		else  //当当前节的的右子树为空,说明它是叶子节点。此时需要判断情况
		{
			//判断当前节点是父亲节点的左孩子还是右孩子
			//第一种情况:如果是cur==parent->_right,即当前节点是父节点的右孩子的话,就
			//去找祖先节点(这个祖先节点的特征:孩子节点是它的左节点)
			//第二种情况:当前节点是父亲节点的左孩子,那么下一个节点就是父亲节点了
			Node* cur = _node;
			//定义父亲节点。
			Node* parent = cur->_parent;
			//如果当前节点不是父亲的右孩子,并且父亲是不存在,那么就是根节点或者是左孩子节点了
			//那就不进入循环。
			while (parent && cur == parent->_right)
			{
				//如果是,那就往上找到下一个节点。
				cur = cur->_parent;
				parent = parent->_parent;
			}

			//更新节点
			_node = parent;

		}
		return *this;
	}

2.begin()和end();

迭代器的begin和end,是左闭右开的区间。那么begin就是最左的节点,end则是最右节点的下一个节点。这里不使用哨兵来作为end节点。

对于begin,就找到最左的节点就好啦!

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

对于end,这里选择直接置为空,传入匿名对象。这样子做的话,当遍历到最后一个节点后,它会往回遍历,一直到了根节点的上面一个节点,也就是空。此时就会结束循环。

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

迭代器整体代码:

template<class ValueType>
struct __RBTreeIterator
{
	typedef RBTreeNode<ValueType> Node;
	typedef __RBTreeIterator<ValueType> Self;
	Node* _node;

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

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

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

	Self& operator++()
	{
		
		if (_node->_right)
		{
			Node* minValue = _node->_right;
			while (minValue->_left)
			{
				minValue = minValue->_left;
			}

		
			_node = minValue;
		}
		else 
		{
			Node* cur = _node;
	
			Node* parent = cur->_parent;
			while (parent && cur == parent->_right)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}
	bool operator!=(const Self& s)
	{
		return _node != s._node;
	}
};

set的模拟实现

仿函数

由于set只有Key,因此就让仿函数直接返回Key值就可以了。

template<class K>
	class set
	{
		//set的仿函数,返回set的K值
		struct SetKeyOfValue
		{

			const K& operator()(const K& key)
			{
				return key;
			}
		};

整体都是复用了红黑树的接口,并没有什么技术含量,问题都在红黑树中解决了。

namespace my_set
{
	template<class K>
	class set
	{
		//set的仿函数,返回set的K值
		struct SetKeyOfValue
		{

			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename RBTree<K, K, SetKeyOfValue>::iterator iterator;

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

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

		bool insert(const K& key)
		{
			return _t.Insert(key);
		}
	private:
		RBTree<K, K, SetKeyOfValue> _t;

	};

map的模拟实现:

仿函数

由于比较的是first,因此我们返回键值对中的first即可。

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

整体代码,一样的都是复用了红黑树的接口,并没有什么技术含量:

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

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

最后是红黑树的整体代码,这段代码跟单独写的红黑树的代码差不多,就是在新增节点的操作那里,比较节点的值使用的是仿函数。

#pragma once

//使用枚举
enum Colour
{
	RED,
	BLACK,
};


// 如果是map,则为pair<K, V>; 如果是set,则为k
//因此,对于节点来说,需要再套一层模板,来应付两种不同的情况
template<class T>
struct RBTreeNode
{
	T _data;
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	Colour _col;

	RBTreeNode(const T& data)
		:_data(data)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(RED)  //默认是红色
	{}
};

template<class ValueType>
struct __RBTreeIterator
{
	typedef RBTreeNode<ValueType> Node;
	typedef __RBTreeIterator<ValueType> Self;
	Node* _node;

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

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

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

	//左 根  右
	Self& operator++()
	{
		//当当前节点的右子树不为空,根据中序遍历的顺序,那就找右子树最小值
		if (_node->_right)
		{
			Node* minValue = _node->_right;
			while (minValue->_left)
			{
				minValue = minValue->_left;
			}

			//找到最小值的节点后,更新节点
			_node = minValue;
		}
		else  //当当前节的的右子树为空,说明它是叶子节点。此时需要判断情况
		{
			//判断当前节点是父亲节点的左孩子还是右孩子
			//第一种情况:如果是cur==parent->_right,即当前节点是父节点的右孩子的话,就
			//去找祖先节点(这个祖先节点的特征:孩子节点是它的左节点)
			//第二种情况:当前节点是父亲节点的左孩子,那么下一个节点就是父亲节点了
			Node* cur = _node;
			//定义父亲节点。
			Node* parent = cur->_parent;
			//如果当前节点不是父亲的右孩子,并且父亲是不存在,那么就是根节点或者是左孩子节点了
			//那就不进入循环。
			while (parent && cur == parent->_right)
			{
				//如果是,那就往上找到下一个节点。
				cur = cur->_parent;
				parent = parent->_parent;
			}

			//更新节点
			_node = parent;

		}
		return *this;
	}

	bool operator!=(const Self& s)
	{
		return _node != s._node;
	}
};
//跟节点的定义一样
//因为关联式容器中存储的是<key, value>的键值对,因此
// k为key的类型,
// ValueType: 如果是map,则为pair<K, V>; 如果是set,则为k。如下:
// map->RBTree<K,pair<const K,V>,MapKeyOfT> _t;
// set->RBTree<K,K,SetKeyOfT> _t
// 
// KeyOfValue: 通过value来获取key的一个仿函数类。其作用就是用于比较。
template<class K,class ValueType,class KeyOfValue>
class RBTree
{
	typedef RBTreeNode<ValueType> Node;
public:
	typedef __RBTreeIterator<ValueType> iterator;

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

	iterator end()
	{
		return iterator(nullptr);
	}
	bool Insert(const ValueType& data)
	{
		//先按二叉搜索树的规矩来创建一棵二叉搜索树
		if (_root == nullptr)
		{
			_root = new Node(data);
			//因为红黑树的根节点是黑色的
			_root->_col = BLACK;
			return true;
		}

		KeyOfValue 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);
		cur->_col = RED;//多写一步,防止写错代码。
		if (kot(parent->_data) < kot(data))
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		//创建完二叉搜索树
		//开始创建红黑树,使用颜色来判断是否需要调整
		//循环往上走,循环条件:当走到的parent不为空,并且parent是红色的
		//即我们列举是三种情况,parent都是红的,就需要重新调整
		//如果parent是黑色的,那就不需要了。直接就是一棵红黑树,不进入循环
		while (parent && parent->_col == RED)
		{
			//保存祖先节点,即g节点
			Node* grandfther = parent->_parent;
			//判断父节点是在祖先节点的哪边
			if (parent == grandfther->_left)
			{
				//父节点在左边,那么叔叔节点就在右边
				Node* uncle = grandfther->_right;
				//情况一:uncle存在且为红。改变颜色即可
				if (uncle && uncle->_col == RED)
				{
					//变色。
					parent->_col = uncle->_col = BLACK;
					grandfther->_col = RED;

					//往上走
					cur = grandfther;
					parent = cur->_parent;
				}
				else  //uncle不存在 或者 存在但是黑色
				{
					//情况二  p是g的左孩子,cur是p的左孩子,以g为轴右单旋
					if (cur == parent->_left)
					{
						//右单旋
						RotateR(grandfther);
						//变色  右单旋后,parent为根节点,变黑色。cur和g节点为红色
						parent->_col = BLACK;
						grandfther->_col = RED;
					}
					else  //情况三  p是g的左孩子,cur是p的右孩子.
					{
						//先以p为轴左旋转
						RotateL(parent);
						//变成情况二,再以g为轴右单旋
						RotateR(grandfther);
						//变色  cur变成根节点,为黑色。p和g是红色
						cur->_col = BLACK;
						grandfther->_col = RED;

					}

					break;
				}
			}
			else  //parent是在grandfther的右边
			{
				//叔叔节点就在祖先节点的左边
				Node* uncle = grandfther->_left;
				//情况一:uncle存在且为红。改变颜色即可
				if (uncle && uncle->_col == RED)
				{
					//变色。
					parent->_col = uncle->_col = BLACK;
					grandfther->_col = RED;

					//往上走
					cur = grandfther;
					parent = cur->_parent;
				}
				else  //uncle不存在 或者 存在但是黑色
				{
					//情况二  p是g的右孩子,cur是p的右孩子。
					if (cur == parent->_right)
					{
						//左单旋
						RotateL(grandfther);
						//变色  右单旋后,parent为根节点,变黑色。cur和g节点为红色
						parent->_col = BLACK;
						grandfther->_col = RED;
					}
					else  //情况三  p是g的右孩子,cur是p的左孩子.
					{
						//先以p为轴右旋转
						RotateR(parent);
						//变成情况二,再以g为轴左单旋
						RotateL(grandfther);
						//变色  cur变成根节点,为黑色。p和g是红色
						cur->_col = BLACK;
						grandfther->_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;
		}

		Node* ppNode = parent->_parent;
		subR->_left = parent;
		parent->_parent = subR;
		if (ppNode == nullptr)
		{
			_root = subR;
			_root->_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;
		}
		Node* ppNode = parent->_parent;
		subL->_right = parent;
		parent->_parent = subL;

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

			}
			else
			{
				ppNode->_right = subL;
			}


			subL->_parent = ppNode;
		}
	}

	void Inorder()
	{
		_Inorder(_root);
	}

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

		_Inorder(root->_left);
		std::cout << root->_kv.first << ": " << root->_kv.second << std::endl;
		_Inorder(root->_right);
	}
	bool Check(Node* root, int blackNum, const int ref)
	{
		if (root == nullptr)
		{
			//cout << blackNum << endl;
			if (blackNum != ref)
			{
				cout << "违反规则:本条路径的黑色节点的数量跟最左路径不相等" << endl;
				return false;
			}

			return true;
		}

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

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

		return Check(root->_left, blackNum, ref)
			&& Check(root->_right, blackNum, ref);
	}

	bool IsBalance()
	{
		if (_root == nullptr)
		{
			return true;
		}

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

		int ref = 0;
		Node* left = _root;
		while (left)
		{
			if (left->_col == BLACK)
			{
				++ref;
			}

			left = left->_left;
		}

		return Check(_root, 0, ref);
	}
private:
	Node* _root = nullptr;
};

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

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

相关文章

教育行业需要什么样的数字产品?

数字化转型的浪潮已经席卷了各行各业&#xff0c;不仅出现在互联网、电商、建筑等行业&#xff0c;还应用在了教育行业。数字化的教育ERP软件能够在满足学校需求的基础上&#xff0c;帮助学校完善各类工作流程&#xff0c;提高工作效率。 对于一个拥有多个校区&#xff0c;上万…

ChatGPT 也太火了吧 ...

最近 ChatGPT 太火了&#xff0c;微信指数 ChatGPT 关键词飙升。GitHub 上也不例外&#xff0c;最近热门项目都是 ChatGPT 项目。后续会陆续更新 ChatGPT 好玩的开源项目&#xff0c;本期是本周登上热榜的 Repo&#xff0c;请查收。本期推荐开源项目目录&#xff1a;1. 对 Chat…

ms17-010(永恒之蓝漏洞复现)

✅作者简介&#xff1a;CSDN内容合伙人、信息安全专业在校大学生&#x1f3c6; &#x1f525;系列专栏 &#xff1a;HW-2023-漏洞复现 &#x1f4c3;新人博主 &#xff1a;欢迎点赞收藏关注&#xff0c;会回访&#xff01; &#x1f4ac;舞台再大&#xff0c;你不上台&#xff…

Inception-Resnet-v1、Inception-Resnet-v2学习笔记

Inception-Resnet-v1、Inception-Resnet-v2来自2016年谷歌发表的这篇论文&#xff1a;Inception-v4 Inception-ResNet and the Impact of Residual Connections on Learning&#xff0c;附论文链接&#xff1a; [1602.07261] Inception-v4, Inception-ResNet and the Impact o…

【思维模型】概率思维的价值:找到你的人生算法!打开你的人生格局!实现认知跃迁!

把同样公平的机会放在放在很多人面前,不同的人生算法,会得到迥然不同的结果。 概率思维是什么? 【ChatGPT】概率思维是一种通过使用数学模型来思考和评估不确定性事件的方法。它通过计算不同可能性的概率来预测事件的结果,并评估风险和机会。 概率思维的价值在于它可以帮…

CSS样式表继承和优先级

CSS样式表继承 要想了解css样式表的继承&#xff0c;我们先从文档树&#xff08;HTML DOM&#xff09;开始。文档树由HTML元素组成。 文档树和家族树类似&#xff0c;也有祖先、后代、父亲、孩子和兄弟_。 那么CSS样式表继承指的是&#xff0c;特定的CSS属性向下传递到子孙元…

智能三子棋(人机大战)—— 你会是最终赢家吗?万字讲解让你实现与自己对弈

魔王的介绍&#xff1a;&#x1f636;‍&#x1f32b;️一名双非本科大一小白。魔王的目标&#xff1a;&#x1f92f;努力赶上周围卷王的脚步。魔王的主页&#xff1a;&#x1f525;&#x1f525;&#x1f525;大魔王.&#x1f525;&#x1f525;&#x1f525; ❤️‍&#x1…

2023年湖北建设厅七大员八大员报名怎么收费呢?

建设厅七大员八大员全国统一报名网站&#xff0c;证书全国通用&#xff0c;无需调转&#xff0c;这点还是很方便的&#xff0c;所有在湖北考的证书全国都能用呢。 八大员报考机构很多&#xff0c;收费也是层次不齐&#xff0c;这里需要提醒大家注意的是&#xff0c;咨询八大员的…

如何持续架构治理?我们和 ChatGPT 聊了一会?

在上周的 QCon 北京 2022 大会上&#xff0c;我和我的同事黄雨青一起分享了《组织级架构治理的正确方式》&#xff0c;以帮助开发人员对组织级架构治理体系全貌一瞥&#xff0c;并厘清治理工具的设计思路和核心功能内容。结合我们在 ArchGuard 的探索经验&#xff0c;我们&…

自有APP上如何运行小游戏?

近年来小程序游戏迎来了爆发式增长。微信、支付宝、抖音等各大平台小程序游戏愈加丰富&#xff0c;你是否也让自己的App也拥有运行丰富的小游戏的能力&#xff1f;今天就来带大家看看如何实现。 我们先来看看各互联网巨头关于小游戏生态的特征&#xff1a; 「微信」 率先推出…

open3d点云配准函数registration_icp

文章目录基本原理open3d调用绘图基本原理 ICP, 即Iterative Closest Point, 迭代点算法。 ICP算法有多种形式&#xff0c;其中最简单的思路就是比较点与点之间的距离&#xff0c;对于点云P{pi},Q{qi}P\{p_i\}, Q\{q_i\}P{pi​},Q{qi​}而言&#xff0c;如果二者是同一目标&am…

如何将一张纹理图贴在模型上

前言 小伙伴们是否有过这样的场景:看到一个精美的3D模型&#xff0c;很想知道它是如何被创作出来的&#xff1f;于是开始了一番搜索引擎查找之后&#xff0c;得知需要建模工具来完成&#xff0c;例如3D Max、Maya、Blender、Photoshop。那么本篇就使用这些工具来完成一个精美的…

Redis【包括Redis 的安装+本地远程连接】

Redis 一、为什么要用缓存&#xff1f; 缓存定义 缓存是一个高速数据交换的存储器&#xff0c;使用它可以快速的访问和操作数据。 程序中的缓存 在我们程序中&#xff0c;如果没有使用缓存&#xff0c;程序的调用流程是直接访问数据库的&#xff1b; 如果多个程序调用一个数…

如何在原始的认知上找回自己

认知、欲望加恐惧&#xff0c;这三种要素在我们对一个事物的判断中都在起作用&#xff0c;只不过配比不一样&#xff0c;导致你的判断不一样。我们通常以为有了认知能力&#xff0c;就产生了认知&#xff0c;就如同面前有一个东西&#xff0c;你用照相机拍下来就成了一张照片—…

【算法基础】高精度除法

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前是C语言 算法学习者 ✈️专栏&#xff1a;【C/C】算法 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac…

PN外加电场后电场变化

没有外加电场时&#xff08;下面都是以外加反向电场分析&#xff09; 中间两条实线假设是PN节在没有外加电场的情况下形成的一个内部电场边界。 形成原因 实用的半导体一般是混合物 P型半导体&#xff0c;实际上是一种4价和3价元素的混合物。化学中都知道达到4或8的外层电子…

论文浅尝 | KGE by Adaptive Limit Scoring Loss Using DWS

笔记整理&#xff1a;陈磊&#xff0c;天津大学硕士链接&#xff1a;https://ieeexplore.ieee.org/ielx7/6287639/7859429/08057770.pdf动机设计一个强大而有效的损失框架对于知识图嵌入模型区分正确和不正确的三元组至关重要。经典的基于边距的排名损失将正负三元组的分数限制…

极智AI | 算能SDK架构

欢迎关注我的公众号 [极智视界]&#xff0c;获取我的更多经验分享 大家好&#xff0c;我是极智视界&#xff0c;本文介绍一下 算能SDK架构。 邀您加入我的知识星球「极智视界」&#xff0c;星球内有超多好玩的项目实战源码下载&#xff0c;链接&#xff1a;https://t.zsxq.com…

华芯微特开发环境搭建-SWM34SVET6为例

SWM34S系列是cortex-M33&#xff0c;内核是arm-v8指令集&#xff0c;和其他cortex系列有差异&#xff0c;要新的工具版本支持&#xff08;jlink要升级到V9以上&#xff0c;keil要升级到5.32以上&#xff09;。 1.Keil要先安装5.36的版本&#xff0c;并取得版权&#xff08;5.3…

【MYSQL中级篇】数据库数据查询学习

&#x1f341;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; 相关文章 文章名文章地址【MYSQL初级篇】入门…