【C++】map和set的封装(红黑树)

news2024/10/1 17:41:09

map和set的封装

  • 一、介绍
  • 二、stl源码剖析
  • 三、仿函数获取数值
  • 四、红黑树的迭代器
  • 五、map的[]
    • 5.1 普通迭代器转const迭代器
  • 六、set源码
  • 七、map源码
  • 八、红黑树源码

一、介绍

首先要知道map和set的底层都是用红黑树实现的
【数据结构】红黑树
set只需要一个key,但是map既有key也有val。
那么我们怎么同时兼容呢?

二、stl源码剖析

在这里插入图片描述

从这张图可以看出红黑树的节点里面存的类型是由Value决定的,跟Key无关。

所以我们实现的时候就可以给RBTree添加一个模板参数

template<class K, class T>
class RBTree

T模板参数我们既可以传K也可以传pair<k, V>
map

template <class K>
class set
{
private:
    RBTree<K,K> _t;
};

set

template <class K, class V>
class map
{
private:
    RBTree<K, pair<const K,V>> _t;
};

既然通过第二个参数就能确定节点的类型,那么第一个参数有什么用呢?

当我们查找的时候,如果是map,第二个参数就是pair类型,不能使用,所以得加上第一个参数,方便查找。

参照stl的方法定义节点:

template <class T>
struct RBTreeNode
{
	RBTreeNode(const T& data)
		: _data(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(RED)
	{}

	T _data;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	Colour _col;
};

三、仿函数获取数值

我们知道红黑树是搜索树,插入的时候需要比较大小,而我们插入的有可能是K,也有可能是pair<K, V>,导致我们无法直接比较。

而stl的做法就是利用仿函数获取我们需要进行比较的元素。
在这里插入图片描述
set

template <class K>
class set
{
	struct SetKeyOfT
	{
		const K& operator()(const K& k)
		{
			return k;
		}
	};
public:
private:
	RBTree<K, K, SetKeyOfT> _t;
};

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<K, V, >, MapKeyOfT> _t;
};

在这里插入图片描述
进行大小比较

KeyOfT kot;// 仿函数比较
Node* parent = nullptr;
Node* cur = _root;
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 false;
}

四、红黑树的迭代器

template <class T, class Ref, class Ptr>
struct RBTIterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTIterator<T, Ref, Ptr> self;
	RBTIterator(Node* node)
		: _node(node)
	{}
	Node* _node;
};

*:解引用操作,返回对应结点数据的引用:

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

->:访问成员操作符,返回节点数据的地址

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

!=、== 比较迭代器是否指向同一节点

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

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

begin()end()
begin():返回的是最左节点(中序遍历的第一个节点)
end():迭代器的end()一般是返回最后一个节点的下一个位置,这里设置为nullptr。

typedef RBTIterator<T, T&, T*> iterator;
typedef RBTIterator<T, const T&, const T*> const_iterator;
iterator begin()
{
	Node* cur = _root;
	while (cur && cur->_left)
	{
		cur = cur->_left;
	}
	return iterator(cur);
}

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

map里面的begin()end()

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

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

这里注意因为编译的时候编译器不知道RBTree<K, pair<const K, V>, MapKeyOfT >::iterator这是个类型还是静态成员变量,会编译出错,加上typename就是告诉编译器这里是一个类型

set的begin()end()

typedef typename RBTree<K, K, SetKeyOfT >::iterator iterator;
iterator begin()
{
	return _t.begin();
}

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

这里重要的是迭代器的++--
++
寻找中序遍历的下一个节点:
1️⃣ 如果右子树不为空++就是找右子树的最左节点。
1️⃣ 如果右子树为空++就是找祖先(孩子是父亲的左的那个祖先)

self& operator++()
{
	if (_node->_right)
	{
		Node* min = _node->_right;
		while (min->_left)
		{
			min = min->_left;
		}
		_node = min;
	}
	else
	{
		Node* cur = _node;
		Node* parent = cur->_parent;
		while (parent && parent->_right == cur)
		{
			cur = parent;
			parent = parent->_parent;
		}
		_node = parent;
	}
	return *this;
}

--
++刚好是反过来:
1️⃣ 如果左子树不为空++就是找左子树的最右节点。
1️⃣ 如果左子树为空++就是找祖先(孩子是父亲的右的那个祖先)

self& operator--()
{
	if (_node->_left)
	{
		Node* max = _node->_left;
		while (max && max->_right)
		{
			max = max->_right;
		}
		_node = max;
	}
	else
	{
		Node* cur = _node;
		Node* parent = cur->_parent;
		while (parent && parent->_left == cur)
		{
			cur = parent;
			parent = parent->_parent;
		}
		_node = parent;
	}
	return *this;
}

这里还有一个重要的问题:
如果这么写那么set的值也可以被修改。那么如何保证set不能被修改呢?

在这里插入图片描述
可以直接把普通迭代器和const迭代器都变成const_iterator。

此时这里会出现问题:

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

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

这里_t是普通对象,会调用普通的迭代器,类型不同,无法返回。
在这里插入图片描述

我们只需要在函数后面加上const就可以权限缩小,变成const对象。

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

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

在红黑树中也要加入对应的const版本begin()end()

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

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

五、map的[]

当我们想使用map来统计次数的时候,就需要重载[]
如果想要支持[],那么insert的返回值就得设置成pair<iterator, bool>
如果在bool就是false,iterator返回当前节点。

return make_pair(iterator(cur), false);

不在就插入。

return make_pair(iterator(newnode), true);

map

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

这里要注意set:

pair<iterator, bool> insert(const K& k)
{
	return _t.insert(k);
}

这里的iterator其实是const_iterator,所以导致类型不同。

5.1 普通迭代器转const迭代器

正常情况下普通迭代器不能转化为const迭代器。
为了解决这种情况,我们在迭代器内添加一个拷贝构造即可。
在这里插入图片描述

1️⃣ 当传进来的是普通迭代器的时候,iterator是普通迭代器,这个函数相当于拷贝构造
2️⃣ 当传进来的是const迭代器的时候,iterator依然是普通迭代器,此时该函数就相当于构造函数(普通迭代构造const迭代器)。

其实普通迭代器和const的区别就在operator*operator->

而set的插入不需要修改:

pair<iterator, bool> insert(const K& k)
{
	return _t.insert(k);
}

return的时候会调用拷贝构造函数,也就是构造函数,把普通迭代器转化为const迭代器。

六、set源码

#pragma once
#include "RBTree.h"


namespace yyh
{
	template <class K>
	class set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& k)
			{
				return k;
			}
		};
	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& k)
		{
			return _t.insert(k);
		}
	private:
		RBTree<K, K, SetKeyOfT> _t;
	};
}

七、map源码

#pragma once
#include "RBTree.h"

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

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

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

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

八、红黑树源码

#pragma once
#include <iostream>
#include <cstdlib>
#include <cassert>
#include <string>

using namespace std;

enum Colour
{
	RED,
	BLACK,
};

template <class T>
struct RBTreeNode
{
	RBTreeNode(const T& data)
		: _data(data)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(RED)
	{}

	T _data;
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	Colour _col;
};

template <class T, class Ref, class Ptr>
struct RBTIterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTIterator<T, Ref, Ptr> self;
	typedef RBTIterator<T, T&, T*> iterator;

	RBTIterator(const iterator& s)
		: _node(s._node)
	{}

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

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

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

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

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

	self& operator++()
	{
		if (_node->_right)
		{
			Node* min = _node->_right;
			while (min->_left)
			{
				min = min->_left;
			}
			_node = min;
		}
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && parent->_right == cur)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}

	self& operator--()
	{
		if (_node->_left)
		{
			Node* max = _node->_left;
			while (max && max->_right)
			{
				max = max->_right;
			}
			_node = max;
		}
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && parent->_left == cur)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}

	Node* _node;
};

template <class K, class T, class KeyOfT>
class RBTree
{
public:
	typedef RBTreeNode<T> Node;
	typedef RBTIterator<T, T&, T*> iterator;
	typedef RBTIterator<T, const T&, const T*> const_iterator;
	iterator begin()
	{
		Node* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return iterator(cur);
	}

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

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

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

	pair<iterator, bool> insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(iterator(_root), true);
		}
		KeyOfT kot;// 仿函数比较
		Node* parent = nullptr;
		Node* cur = _root;
		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);
		Node* newnode = cur;
		if (kot(data) < kot(parent->_data))
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		while (parent && parent->_col == RED)
		{
			// 找g 与 u
			Node* g = parent->_parent;
			if (parent == g->_left)
			{
				Node* u = g->_right;
				// 情况一 u存在且为红
				if (u && u->_col == RED)
				{
					parent->_col = u->_col = BLACK;
					g->_col = RED;
					// 继续往上处理
					cur = g;
					parent = cur->_parent;
				}
				else // 情况二或情况三
				{
					if (cur == parent->_left)// 情况二
					{
						//   g
						//  p
						// c
						RotateR(g);
						parent->_col = BLACK;
						g->_col = RED;
					}
					else// 情况三
					{
						//  g
						// p
						//  c
						RotateL(parent);
						RotateR(g);
						//   c
						// p   g
						cur->_col = BLACK;
						g->_col = RED;
					}
					break;
				}
			}
			else
			{
				Node* u = g->_left;
				// 情况一
				if (u && u->_col == RED)
				{
					u->_col = parent->_col = BLACK;
					g->_col = RED;
					cur = g;
					parent = cur->_parent;
				}
				else
				{
					// 情况二
					// g
					//  p
					//   c
					if (cur == parent->_right)
					{
						RotateL(g);
						parent->_col = BLACK;
						g->_col = RED;
					}
					else// 情况三
					{
						// g
						//  p
						// c
						RotateR(parent);
						RotateL(g);
						cur->_col = BLACK;
						g->_col = RED;
					}
					break;
				}
			}
		}
		// 上面有可能把_root的颜色变为红
		_root->_col = BLACK;
		return make_pair(iterator(newnode), true);
	}

	void RotateL(Node* parent)
	{
		Node* top = parent->_parent;
		Node* right = parent->_right;
		parent->_right = right->_left;
		if (right->_left) right->_left->_parent = parent;
		right->_left = parent;
		parent->_parent = right;
		if (top)// 子树
		{
			if (parent == top->_left) top->_left = right;
			else top->_right = right;
			right->_parent = top;
		}
		else// 完整的树
		{
			_root = right;
			_root->_parent = nullptr;
		}
	}

	void RotateR(Node* parent)
	{
		Node* top = parent->_parent;
		Node* left = parent->_left;
		Node* leftR = left->_right;
		parent->_left = leftR;
		if (leftR) leftR->_parent = parent;
		left->_right = parent;
		parent->_parent = left;
		if (top)
		{
			if (parent == top->_left) top->_left = left;
			else top->_right = left;
			left->_parent = top;
		}
		else
		{
			_root = left;
			_root->_parent = nullptr;
		}
	}

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

		_Inorder(root->_left);
		cout << root->_kv.first << "<=>" << root->_kv.second << endl;
		_Inorder(root->_right);
	}

	void Inorder()
	{
		_Inorder(_root);
	}

	bool _IsBalance(Node* root, int i, int flag)
	{
		if (root == nullptr)
		{
			if (i != flag)
			{
				cout << "errno: 左右子树黑色节点数目不同" << endl;
				return false;
			}
			return true;
		}
		// 红节点时判断父亲
		if (root->_col == RED)
		{
			if (root->_parent->_col == RED)
			{
				cout << "errno: 红-红" << endl;
				return false;
			}
		}
		if (root->_col == BLACK)
		{
			i++;
		}

		return _IsBalance(root->_left, i, flag) 
			&& _IsBalance(root->_right, i, flag);
	}

	bool IsBalance()
	{
		if (_root == nullptr)
		{
			return true;
		}
		if (_root->_col != BLACK)
		{
			return false;
		}
		// 找标准值
		Node* cur = _root;
		int flag = 0;
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				flag++;
			}
			cur = cur->_left;
		}
		int i = 0;
		return _IsBalance(_root, i, flag);
	}

private:
	Node* _root = nullptr;
};

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

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

相关文章

分布式-分布式消息笔记

消息队列应用场景 消息队列 消息队列是进程之间的一种很重要的通信机制。参与消息传递的双方称为生产者和消费者&#xff0c;生产者和消费者可以只有一个实例&#xff0c;也可以集群部署。 消息体是参与生产和消费两方传递的数据&#xff0c;消息格式既可以是简单的字符串&am…

MYSQL安装部署 - Linux 本地安装及卸载

声明 &#xff1a;# 此次我们安装的 MYSQL 版本是 8.0.32 版本我们本次安装 MYSQL 总共要介绍 四种方式# 仓库安装# 本地安装# 容器安装# 源码安装我们本篇介绍的是 本地安装 我们还是去官网下载 &#xff1a;我们就是找着 .bundle.tar 这个包&#xff0c;里面就包含了所有 mys…

功率信号源有什么作用和功能呢

功率信号源是指集信号发生器与功率放大器为一体的电子测量仪器&#xff0c;它具有高电压、大功率的特点&#xff0c;在电子实验室中能够帮助用来驱动压电陶瓷、换能器以及电磁线圈等&#xff0c;可以有效的帮助电子工程师解决驱动负载和放大功率的问题。功率信号源和功率放大器…

过滤器,监听器,拦截器的原理与在Servlet和Spring的应用

在Java Web的开发中&#xff0c;最原始和初期的学习都是从Servlet开始的&#xff0c;Servlet是Java最为耀眼的技术&#xff0c;也是Java EE的技术变革。目前大火主流的框架spring boot也的spring mvc部分也是基于拓展servlet完成的。回到之前的文章spring 实现了对servlet的封装…

SQL语句大全(详解)

SQL前言1 DDL1.1 显示所包含的数据库1.2 创建数据库1.3 删除数据库1.4 使用数据库1.4.1 创建表1.4.2 查看表的结构1.4.3 查看当前数据库下的所有表1.4.4 基础的增删改查1.4.4.1 删除表1.4.4.2 添加列1.4.4.3 修改表名1.4.4.4 修改数据类型1.4.4.5 修改列名和数据类型2 DML2.1 给…

http协议简介

http 1.简介 超文本传输协议&#xff08;HTTP&#xff0c;HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。所有的WWW文件都必须遵守这个标准。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。1960年美国人Ted Nelson构思了一种通过计算机处…

Python实现GWO智能灰狼优化算法优化循环神经网络分类模型(LSTM分类算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。1.项目背景灰狼优化算法(GWO)&#xff0c;由澳大利亚格里菲斯大学学者 Mirjalili 等人于2014年提出来的一种群智能优…

【玩转c++】git的安装和使用以及可视化处理

本期主题&#xff1a;git的安装和使用&#xff08;windows环境&#xff09;博客主页&#xff1a;小峰同学分享小编的在Linux中学习到的知识和遇到的问题 小编的能力有限&#xff0c;出现错误希望大家不吝赐1.两个工具介绍第一个工具git&#xff0c;链接gitee或者github等代码托…

B端产品经理如何做好客户访谈?

用户访谈的价值我们在做用户研究的时候&#xff0c;经常会采用问卷调查和数据分析等定量的研究方法&#xff0c;从中我们可以搜集很多有价值的数据。但是&#xff0c;这些数据往往只停留在表层&#xff0c;没有办法基于用户的场景了解用户行为动机及诉求&#xff0c;很难找到用…

MySql的安装

版本选择 MySql目前使用最稳定的版本为5.7系列版本,尝鲜可以直接去官网链接整最新版本哦 本文使用8.0.32版本,注意这里看一下电脑位数,现在个人电脑一般都是64bit 点击223.6M那个压缩包开始下载,下面那个压缩包包含了测试相关内容一般是不需要的 这里有时会提示要你登录账户…

【REACT-@reduxjs/toolkit+react-redux+redux-persist状态管理】

REACT-reduxjs/toolkitreact-reduxredux-persist状态管理1. 依赖包安装2. 目录结构3. 修改Index.js4. createSlice&#xff08;&#xff09;4.1 action处理4.1.1 创建collapsedSlice4.1.2 使用collapsedSlice4.2 异步action处理4.2.1 使用redux-thunk方式处理异步4.2.1.1 创建a…

Java+MySQL汽车租赁管理系统课程设计

wx供重浩&#xff1a;创享日记 对话框发送&#xff1a;汽车租赁系统 获取完整源码源文件视频演示文档资料等 一、 课程设计目的 1、综合运用Java程序设计课程和其他相关课程的理论和知识&#xff0c;掌握面向对象程序设计的一般方法、常用技术及技巧&#xff0c;树立良好的软件…

MongoDB安装(新版本保姆级教程)

前言MongoDB 是一个文档数据库&#xff0c;旨在简化开发和扩展。本篇文章介绍MongoDB 数据库及其 可视化工具 MongoDB Compass 的详细教程 (window10操作系统)下载安装包首先进入官网(社区版) &#xff0c;在对应页面选择需要安装的版本 (这里下载当前适合版本号)传送门安装因为…

CRI-O, Containerd, Docker, Postman等概念介绍

参考&#xff1a;Docker&#xff0c;containerd&#xff0c;CRI&#xff0c;CRI-O&#xff0c;OCI&#xff0c;runc 分不清&#xff1f;看这一篇就够了Docker, containerd, CRI-O and runc之间的区别&#xff1f; Docker、Podman、Containerd 谁才是真正王者&#xff1f;CRI-O …

最简单的代码生成器,smartsofthelp netframework ,EF 架构

1.原生 sql操作公共类 dbhelper2.model 带注释的实体3.EF功能访问调用类3.EFData EF数据接口层4.UI展示层数据库脚本 自动生成Model /// <summary>/// Model实体层 /// </summary>namespace Smart.Model{/// <summary>/// 数据实体层 T_Eventsmart 投屏事件/…

CI/CD | 深入研究Jenkins后,我挖掘出了找到了摆脱低效率低下的方法

在本系列的第一篇文章中&#xff0c;您已经了解了一些关于如何管理Jenkins的内容&#xff0c;主要是为无序的人带来秩序。在这篇文章中&#xff0c;我将更深入地探讨我效率低下的问题&#xff0c;提出我们工作流中一些安全性、治理和合规性的挑战。这不仅仅是你在网站上或展览横…

从此不怕被盗号:教你如何用 Python 制作一个密码生成器

原由&#xff1a; 定期更换密码是一种非常重要的安全措施&#xff0c;这种做法可以有效地保护你的账户和个人信息不受黑客和网络攻击者的侵害。 密码泄露是一个非常普遍的问题&#xff0c;许多人的账户和密码经常会被泄露出来&#xff0c;导致个人信息被盗用、金融损失、恶意…

hive建分区表,分桶表,内部表,外部表

hive建分区表&#xff0c;分桶表&#xff0c;内部表&#xff0c;外部表 一、概念介绍 Hive是基于Hadoop的一个工具&#xff0c;用来帮助不熟悉 MapReduce的人使用SQL对存储在Hadoop中的大规模数据进行数据提取、转化、加载。Hive数据仓库工具能将结构化的数据文件映射为一张数…

Redis 如何实现库存扣减操作和防止被超卖

在日常开发中有很多地方都有类似扣减库存的操作&#xff0c;比如电商系统中的商品库存&#xff0c;抽奖系统中的奖品库存等。其基本的流程如下:1 解决方案使用mysql数据库&#xff0c;使用一个字段来存储库存&#xff0c;每次扣减库存去更新这个字段。还是使用数据库&#xff0…

自动视觉锁螺丝机及其控制系统

1.全自动智能锁螺丝机的意义电子消耗品、家用电器、汽车零件、音箱、安防、玩具、LED 等制造业的生产过程中&#xff0c;产品组装是一个非常重要的步骤&#xff0c;而螺丝锁付是组装过程的核心工艺之一。当下国内智能手机、智能汽车、智能家电等产品更新换代快&#xff0c;产品…