用红黑树封装出map与set

目录

一、红黑树的改造

节点结构的定义

 迭代器类的实现

红黑树中提供迭代器

红黑树的主要代码

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第一个fyne应用

第一个fyne应用 由于在写一个milvus的图形化工具&#xff0c;方便客户端使用&#xff0c;调研了一下只有这fyne的go-gui的star最多&#xff0c;比较流行&#xff0c;因此打算使用这个框架来进行milvus的工具开发。 第一个fyne应用 依赖go.mod: module fynedemogo 1.20requi…

【自然语言处理】【大模型】DeepSeek-V2论文解析

论文地址&#xff1a;https://arxiv.org/pdf/2405.04434 相关博客 【自然语言处理】【大模型】DeepSeek-V2论文解析 【自然语言处理】【大模型】BitNet&#xff1a;用1-bit Transformer训练LLM 【自然语言处理】BitNet b1.58&#xff1a;1bit LLM时代 【自然语言处理】【长文本…

k8s环境部署的集成arthas-spring-boot-starter spingboot项目无法访问控制台

前言 k8s环境部署的集成arthas-spring-boot-starter项目无法访问控制台&#xff0c;springboot项目集成arthas-spring-boot-starter 会自带个控制台 供我们访问 但是当使用k8s环境部署后 这个页面就无法访问了 分析 首先看下arthas对应的配置 arthas-spring-boot-starter 中…

数据结构(C):树的概念和二叉树初见

目录 &#x1f37a;0.前言 1.树概念及结构 2.认识一棵树 3.树的表示 3.1树在实际中的运用&#xff08;表示文件系统的目录树结构&#xff09; 4.二叉树 4.1特殊的二叉树 4.2二叉树的性质 &#x1f48e;5.结束语 &#x1f37a;0.前言 言C之言&#xff0c;聊C之识&…

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

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

实验三:机器学习1.0

要求&#xff1a; 针对实验1和实验2构建的数据集信息分析 设计实现通过数据简介进行大类分类的程序 代码实现&#xff1a; 训练集数据获取&#xff1a; read_data.py import json import pickledef read_intro():data []trypathr"E:\Procedure\Python\Experiment\f…

【计算机毕业设计】springboot城市公交运营管理系统

二十一世纪我们的社会进入了信息时代&#xff0c; 信息管理系统的建立&#xff0c;大大提高了人们信息化水平。传统的管理方式对时间、地点的限制太多&#xff0c;而在线管理系统刚好能满足这些需求&#xff0c;在线管理系统突破了传统管理方式的局限性。于是本文针对这一需求设…

wefaf

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

算法学习(7)-树

目录 开启“树”之旅 二叉树 堆--优先队列 并查集 开启“树”之旅 是不是很像一棵倒挂的树&#xff1f;也就是说它是根朝上&#xff0c; 而叶子朝下的。不像&#xff1f;哈哈&#xff0c;来看看下面的图你就会觉得像啦。 你可能会间&#xff1a; 树和图有什么区别&#xff…

纯血鸿蒙APP实战开发——Worker子线程中解压文件

介绍 本示例介绍在Worker 子线程使用ohos.zlib 提供的zlib.decompressfile接口对沙箱目录中的压缩文件进行解压操作&#xff0c;解压成功后将解压路径返回主线程&#xff0c;获取解压文件列表。 效果图预览 使用说明 点击解压按钮&#xff0c;解压test.zip文件&#xff0c;显…

ArcGIS10.X入门实战视频教程(arcgis入门到精通)

点击学习&#xff1a; ArcGIS10.X入门实战视频教程&#xff08;GIS思维&#xff09;https://edu.csdn.net/course/detail/4046?utm_sourceblog2edu 点击学习&#xff1a; ArcGIS10.X入门实战视频教程&#xff08;GIS思维&#xff09;https://edu.csdn.net/course/detail/404…

用SwitchHosts模拟本地域名解析访问

一.用SwitchHosts模拟本地域名解析访问 1.下载地址 https://download.csdn.net/download/jinhuding/89313168 2.使用截图

Python自动化SQL注入和数据库取证工具库之sqlmap使用详解

概要 在网络安全领域,SQL注入仍然是最常见的攻击之一。sqlmap是一个开源的自动化SQL注入和数据库取证工具,它提供了广泛的功能来检测和利用SQL注入漏洞。本文将详细介绍sqlmap的安装、特性、基本与高级功能,并结合实际应用场景,展示其在网络安全测试中的应用。 安装 sqlm…

激光打标机:手机制造中不可或缺的加工设备

激光打标机在手机行业中有多种应用&#xff0c;主要体现在以下几个方面&#xff1a; 1. 手机外壳打标&#xff1a;光纤激光打标机在手机外壳上打标的痕迹非常美观&#xff0c;可以印上厂家品牌标识&#xff0c;既保证了手机外壳的美观&#xff0c;也提高了产品的打标质量和加工…

云曦实验室期中考核题

Web_SINGIN 解题&#xff1a; 点击打开环境&#xff0c;得 查看源代码&#xff0c;得 点开下面的超链接&#xff0c;得 看到一串base64编码&#xff0c;解码得flag 简简单单的文件上传 解题&#xff1a; 点击打开环境&#xff0c;得 可以看出这是一道文件上传的题目&#x…

2024年最新软件测试面试题必问的1000题!

我了解的测试理论和方法包括以下几个方面&#xff1a; 黑盒测试与白盒测试&#xff1a; 黑盒测试&#xff1a;基于对软件系统外部行为进行测试&#xff0c;独立于内部代码实现细节。黑盒测试关注输入与输出之间的关系以及软件功能是否符合预期。白盒测试&#xff1a;基于对软件…

搭载全新升级viaim AI,讯飞会议耳机Pro 2首销价1399元起

2024年5月15日&#xff0c;人工智能硬件公司未来智能发布了讯飞会议耳机Pro 2、iFLYBUDS 2以及Kit 2三款旗舰新品&#xff0c;为用户带来全新升级的viaim AI&#xff0c;也为AIGC智能耳机树立了新标杆。 在发布会上&#xff0c;未来智能CEO马啸表示&#xff1a;在AIGC领域&…

基于EBAZ4205矿板的图像处理:05均值滤波算法

基于EBAZ4205矿板的图像处理&#xff1a;05均值滤波算法 项目全部文件已经上传&#xff0c;是免费的 先看效果 可以明显看到图像变糊了&#xff0c;这就是均值滤波的特点&#xff0c;将噪声均摊到每个点上的同时&#xff0c;也会让图像丢失细节。 算法讲解 均值滤波&#x…

连锁收银系统如何助力实体门店私域运营

作为实体门店&#xff0c;私域运营是提升客户黏性和增加复购率的重要策略之一。而连锁收银系统在私域运营中扮演了关键的角色&#xff0c;它不仅可以帮助门店管理客户信息和消费记录&#xff0c;还能够通过数据分析和营销功能提供个性化的服务和推广活动。下面看看连锁收银系统…

STM32F407 2个高级定时器生成2路无刷电机波形以及相电流采集程序(寄存器版)

stm32f407 高级定时1、定时8 生成20k 中心PWM 波形 并分别用其通道4 触发ADC1 ADC2 采样 用于分别两无刷电机foc 电流环控制&#xff0c;ADC1产生50us的电流采集完成中断&#xff0c;用于foc算法周期运算 主要参考高级定时器的寄存器和ADC寄存器 首先&#xff0c;要使用STM32F…