unordered-------Hash

✅<1>主页:我的代码爱吃辣
📃<2>知识讲解:数据结构——哈希表
☂️<3>开发环境:Visual Studio 2022
💬<4>前言:哈希是一种映射的思想,哈希表即使利用这种思想,在查找上进行很少的比较次数就能够将元素找到,非常的高效,在一定程度上,效率比红黑树还要强,因此在C++11中,STL又提供了4个unordered系列的关联式容器,他们的底层就是哈希。

目录

一.unordered系列关联式容器

1. unordered_map

1.1 unordered_map的构造

1.2unordered_map的容量

 1.3unordered_map的迭代器

1.4 unordered_map的元素访问

1.5unordered_map的查询 

1.6unordered_map的修改操作

 1.2unordered_set

二.哈希

1.哈希概念

2. 哈希冲突

3.哈希冲突解决

三.实现闭散列除留余数法+线性探测

1.整体结构

2.插入

3.查询

4.删除

四.开散列

1.开散列实现

2.插入

3.查询

4.删除

5.析构函数

五.完整代码即测试


 

一.unordered系列关联式容器

在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到 log{_{2}}^{N},即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好
的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同,本文中只对unordered_map和unordered_set进行介绍,unordered_multimap和unordered_multiset学生可查看文档介绍。

1. unordered_map

reference-------unordered_map

  1. unordered_map是存储<key, value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value。
  2. 在unordered_map中,键值通常用于惟一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
  3. 在内部,unordered_map没有对<kye, value>按照任何特定的顺序排序, 为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中。
  4. unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
  5. unordered_maps实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value。
  6. 它的迭代器至少是前向迭代器。

1.1 unordered_map的构造

函数声明功能介绍
unordered_map构造不同格式的unordered_map对象

1.2unordered_map的容量

 

函数声明功能介绍
bool empty() const检测unordered_map是否为空
size_t size() const获取unordered_map的有效元素个数

 1.3unordered_map的迭代器

函数声明功能介绍
begin返回unordered_map第一个元素的迭代器
end返回unordered_map最后一个元素下一个位置的迭代器
cbegin返回unordered_map第一个元素的const迭代器
cend返回unordered_map最后一个元素下一个位置的const迭代器

1.4 unordered_map的元素访问

函数声明功能介绍
operator[]返回与key对应的value,没有一个默认值

注意:该函数中实际调用哈希桶的插入操作,用参数key与V()构造一个默认值往底层哈希桶
中插入,如果key不在哈希桶中,插入成功,返回V(),插入失败,说明key已经在哈希桶中,
将key对应的value返回。 

1.5unordered_map的查询 

函数声明功能介绍
iterator find(const K& key)返回key在哈希桶中的位置
size_t count(const K& key)返回哈希桶中关键码为key的键值对的个数

1.6unordered_map的修改操作

函数声明功能介绍
insert向容器中插入键值对
erase删除容器中的键值对
void clear()清空容器中有效元素个数
void swap(unordered_map&)交换两个容器中的元素

 1.2unordered_set

reference-------unordered_set

二.哈希

unordered系列的关联式容器之所以效率比较高,是因为其底层使用了哈希结构。

1.哈希概念

顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素
时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即
O(log{_{2}}^{N}),搜索的效率取决于搜索过程中元素的比较次数。

理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素
如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立
一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

当向该结构中:

  • 插入元素

根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放。

  • 搜索元素

对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置
取元素比较,若关键码相等,则搜索成功。

 该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称
为哈希表(Hash Table)(或者称散列表)。

例如:数据集合{1,7,6,4,5,9};
哈希函数设置为:hash(key) = key % capacity; capacity为存储元素底层空间总的大小。

 用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快
问题:按照上述哈希方式,向集合中插入元素44,会出现什么问题?发生位置冲突。

2. 哈希冲突

对于两个数据元素的关键字k1和k2,有k1 != k2,但有:Hash(k1) ==
Hash(k2),即:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突
或哈希碰撞。

把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。

哈希碰撞的产生一部分原因是,哈希函数设计的不够合理。

哈希函数设计原则:

  • 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间
  • 哈希函数计算出来的地址能均匀分布在整个空间中
  • 哈希函数应该比较简单

 常见哈希函数:

1.直接定址法--(常用)
取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B
优点:简单、均匀。
缺点:需要事先知道关键字的分布情况。
使用场景:适合查找比较小且连续的情况。

2. 除留余数法--(常用)
设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,
按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址。


3. 平方取中法--(了解)
假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址;
再比如关键字为4321,对它平方就是18671041,抽取中间的3位671(或710)作为哈希地址
平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况。


4. 折叠法--(了解)
折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这
几部分叠加求和,并按散列表表长,取后几位作为散列地址。
折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况。


5. 随机数法--(了解)
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中
random为随机数函数。通常应用于关键字长度不等时采用此法。

6. 数学分析法--(了解)
设有n个d位数,每一位可能有r种不同的符号,这r种不同的符号在各位上出现的频率不一定
相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只
有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散
列地址。例如:

 假设要存储某家公司员工登记表,如果用手机号作为关键字,那么极有可能前7位都是 相同
的,那么我们可以选择后面的四位作为散列地址,如果这样的抽取工作还容易出现 冲突,还
可以对抽取出来的数字进行反转(如1234改成4321)、右环位移(如1234改成4123)、左环移
位、前两数与后两数叠加(如1234改成12+34=46)等方法。

数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的
若干位分布较均匀的情况。

注意:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突。

3.哈希冲突解决

解决哈希冲突两种常见的方法是:闭散列和开散列。

闭散列:

闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有
空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。
那如何寻找下一个空位置
呢?

1. 线性探测

比如下面中的场景,现在需要插入元素44,先通过哈希函数计算哈希地址,hashAddr为4,
因此44理论上应该插在该位置,但是该位置已经放了值为4的元素,即发生哈希冲突。
线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。

  •  插入

通过哈希函数获取待插入元素在哈希表中的位置如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素。

  • 删除

采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素
会影响其他元素的搜索。比如删除元素4,如果直接删除掉,44查找起来可能会受影
响。因此线性探测采用标记的伪删除法来删除一个元素。

// 哈希表每个空间给个标记
// EMPTY此位置空, EXIST此位置已经有元素, DELETE元素已经删除
enum State{EMPTY, EXIST, DELETE};

三.实现闭散列除留余数法+线性探测

1.整体结构

	
    //状态  
    enum State{	EXIST,EMPTY,DELETE	};

    //数据结点
	template<class K, class V>
	struct HashDate 
	{
		HashDate()
		{
		}
		HashDate(pair<K,V> kv)
			:_kv(kv)
		{}

		pair<K, V> _kv;
		State state = EMPTY;
	};
    
    //开放定址法哈希表
    template<class K, class V>
	class HashTable
	{
		typedef HashDate<K, V> Date;
	public:
        //插入
		bool insert(pair<K, V> kv)
		{
			
		}
        
        //删除
		bool erase(const K& key)
		{
			
		}
        //查询
		pair<int,bool> find(const K& key)
		{
			
		}

	private:
		vector<Date> _sh;    //数据存储
		size_t _n = 0;       //存储数据的个数
	};
}

2.插入

插入的步骤:

  1. 计算插入位置
  2. 线性探测
        bool insert(pair<K, V> kv)
		{

			//开放定值法,线性探测解决冲突
            //1.计算插入位置
			int hashi = kv.first % _sh.size();
			int i = 1; int hashn = hashi;
            //r如果插入的位置已经有数据了,进行线性探测
			while (_sh[hashn].state == EXIST)
			{
				hashn = hashi + i;
				hashn %= _sh.size();
				i++;
			}
            //探测空位置,插入数据
			_sh[hashn] = Date(kv);
            //修改状态
			_sh[hashn].state = EXIST;
			_n++;
			return true;
		}

注意:在计算插入位置时,我们楚留余数法,使用的是表的size,而不是capacity,是因为我们真正合法插入的位置,是size控制的。

扩容:

哈希表什么情况下进行扩容?如何扩容?

这里引入一个新的概念:负载因子

 我们约定当负载因子达到0.7时就扩容,扩容的步骤:

    bool insert(pair<K, V> kv)
		{
			//如果插入的值已经存在
			if (find(kv.first).second)
			{
				return false;
			}

			//扩容
			//负载因子为0.7
			if (_sh.size() == 0 || _n * 10 / _sh.size() == 7)
			{
				//1.确定新的容量
				int newsize = _sh.size() == 0 ? 10 : _sh.size() * 2;
				//每一次扩容都要重新插入数据
				//2.创建新的哈希表
				HashTable<K, V> newHash;
				//3.将原表数据插入进去,复用已经实现的逻辑
				newHash._sh.resize(newsize);
				for (auto e : _sh)
				{
					newHash.insert(e._kv);
				}
				//最后将新表与旧表交换即可
				_sh.swap(newHash._sh);
			}

			//开放定值法,线性探测解决冲突
			int hashi = kv.first % _sh.size();
			int i = 1; int hashn = hashi;
			while (_sh[hashn].state == EXIST)
			{
				hashn = hashi + i;
				hashn %= _sh.size();
				i++;
			}
			_sh[hashn] = Date(kv);
			_sh[hashn].state = EXIST;
			_n++;
			return true;
		}

3.查询

查询步骤:

  1. 判空
  2. 探测查找
pair<K,bool> find(const K& key)
		{
			//如果表中是空的,返回坐标,和插入false
			if (_sh.empty())
			{
				return pair<K, bool>(-1, false);
			}
			//计算位置
			size_t hashi = key % _sh.size();
			int i = 1; int hashn = hashi;
			//线性探测直到遇到空
			while (_sh[hashn].state!=EMPTY)
			{
				if (_sh[hashn].state == EXIST && _sh[hashn]._kv.first == key)
				{
					//如果探测到,存在且键值相等,就返回该键值和ture
					return pair<K, bool>(hashn,true);
				}
				hashn = hashi + i;
				hashn %= _sh.size();
				i++;
				if (hashn == hashi)
				{
					//已经探测一圈回到了原点,即没找到
					return pair<K, bool>(-1, false);
				}
			}
			//遇到空没找到
			return pair<K, bool>(-1, false);
		}

4.删除

	    //删除
        bool erase(const K& key)
		{
			//先查询,存在就删除,不存在直接返回false
			pair<int, bool> retfind = find(key);
			if (!retfind.second)
			{
				return false;
			}
            //删除后修改状态
			_sh[retfind.first].state = DELETE;
			return true;
		}

线性探测优点:实现非常简单。
线性探测缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同
关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降
低。
如何缓解呢?

四.开散列

开散列概念:

开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地
址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链
接起来,各链表的头结点存储在哈希表中。

 从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素。

1.开散列实现

 开散列的结点和结构:

    template<class K, class V>
	struct HashDate
	{
		HashDate(pair<K, V> kv)
			:_kv(kv),
			_next(nullptr)
		{}

		pair<K, V> _kv;   //键值对
		HashDate* _next;  //下一个结点的指针
	};

	template<class K,class V>
	class Hashbucket
	{
		typedef HashDate<K, V> Date;
		typedef pair<K, V> KV;
	public:

		bool insert(KV kv)
		{
			//...
		}

		Date* find(const K& key)
		{
			//....
		}

		bool erase(const K& key)
		{
			//...
		}
		~Hashbucket()
		{
			//....
		}

	private:
		vector<Date*> _table;  //数据存储
		size_t _n = 0;         //数据存储个数
	};
}

2.插入

因为_table的表中存储都是每个结点的指针,每一个桶实际上就是一个链表,所以对哈希的插入实际就是对链表的头插。

bool insert(KV kv)
{

	//插入
    //计算桶的位置
	size_t hashi = kv.first % _table.size();
    //创建结点
	Date* newNode = new Date(kv);
     //新节点的next指向头节点
	newNode->_next = _table[hashi];
     //新插入的结点变成新的头
	_table[hashi] = newNode;
	_n++;
}

扩容:

约定当数据个数达到桶的个数时,进行扩容:

扩容步骤:

bool insert(KV kv)
		{
			//如果待插入的数据已经存在
			if ( find(kv.first))
			{
				return false;
			}

			//扩容
			if (_n == _table.size())
			{
				//计算新的桶数
				size_t newsize = _table.size() == 0 ? 10 : _table.size() * 2;
				//创建一个新的表
				vector<Date*> newtable;
				newtable.resize(newsize);
				//将就表中的结点拿过来,注意此处直接拿旧的结点插入新表,
				//而不是拿到数据后创建新的结点,减少不必要的消耗
				for (auto& cur : _table)
				{
					while (cur)
					{
						size_t hashi = cur->_kv.first % newtable.size();
						Date* next = cur->_next;
						cur->_next = newtable[hashi];
						newtable[hashi] = cur;
						cur = next;
					}
				}
				//新旧表交换
				_table.swap(newtable);
			}

			//插入
			size_t hashi = kv.first % _table.size();
			Date* newNode = new Date(kv);
			newNode->_next = _table[hashi];
			_table[hashi] = newNode;
			_n++;
		}

3.查询

查询步骤:

  1. 找到桶
  2. 遍历桶中数据

找到了返回结点指针,没找到返回nullptr

		Date* find(const K& key)
		{
			//如果表是空的,即就是没有一个数据
			if (_table.empty())
			{
				return nullptr;
			}
			//1.计算桶的位置
			size_t hashi = key % _table.size();
			Date* cur = _table[hashi];
			//2.在桶里面遍历查询
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			return nullptr;
		}

4.删除

删除归根结底还是链表的删除:

删除的步骤:

  1. 判断在不在表中
  2. 计算出桶的位置
  3. 找打待删除结点,并记录其前一个结点
  4. 改变指向,释放结点

 

    bool erase(const K& key)
		{
			//1.查找在不在表中
			if (!find(key))
			{
				return false;
			}
			//2.计算桶的位置
			size_t hashi = key % _table.size();
			Date* cur = _table[hashi];
			//记录前驱结点
			Date* prov = nullptr;
			//3.找到待删除的结点
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					if (prov == nullptr)
					{
						_table[hashi] = cur->_next;	
					}
					else
					{
						prov->_next = cur->_next;
					}
					//4.删除改变指向,释放结点
					delete cur;
					return true;
				}
				else
				{
					prov = cur;
					cur = cur->_next;
				}
			}
		}

5.析构函数

遍历表中每一个桶,并对每一个桶进行释放,对桶的销毁,就是对链表的销毁。

		~Hashbucket()
		{
			for (auto& cur : _table)
			{
				while (cur)
				{
					Date* next = cur->_next;
					delete cur;
					cur = next;
				}
				cur = nullptr;
			}
		}

五.完整代码即测试

#pragma once
#include<vector>
#include<iostream>
using namespace std;

namespace HashOpenAdress
{
	enum State
	{
		EXIST,
		EMPTY,
		DELETE
	};

	template<class K, class V>
	struct HashDate
	{
		HashDate()
		{
		}
		HashDate(pair<K,V> kv)
			:_kv(kv)
		{}

		pair<K, V> _kv;
		State state = EMPTY;
	};

	template<class K, class V>
	class HashTable
	{
		typedef HashDate<K, V> Date;
	public:
		bool insert(pair<K, V> kv)
		{
			//如果插入的值已经存在
			if (find(kv.first).second)
			{
				return false;
			}

			//扩容
			//负载因子为0.7
			if (_sh.size() == 0 || _n * 10 / _sh.size() == 7)
			{
				//确定新的容量
				int newsize = _sh.size() == 0 ? 10 : _sh.size() * 2;
				//每一次扩容都要重新插入数据
				HashTable<K, V> newHash;
				newHash._sh.resize(newsize);
				for (auto e : _sh)
				{
					newHash.insert(e._kv);
				}
				_sh.swap(newHash._sh);
			}

			//开放定值法,线性探测解决冲突
			int hashi = kv.first % _sh.size();
			int i = 1; int hashn = hashi;
			while (_sh[hashn].state == EXIST)
			{
				hashn = hashi + i;
				hashn %= _sh.size();
				i++;
			}
			_sh[hashn] = Date(kv);
			_sh[hashn].state = EXIST;
			_n++;
			return true;
		}

		bool erase(const K& key)
		{
			pair<int, bool> retfind = find(key);
			if (!retfind.second)
			{
				return false;
			}
			_sh[retfind.first].state = DELETE;
			return true;
		}

		pair<int,bool> find(const K& key)
		{
			if (_sh.empty())
			{
				return pair<int, bool>(-1, false);
			}
			size_t hashi = key % _sh.size();
			int i = 1; int hashn = hashi;
			while (_sh[hashn].state!=EMPTY)
			{
				if (_sh[hashn].state == EXIST && _sh[hashn]._kv.first == key)
				{
					return pair<int,bool>(hashn,true);
				}
				hashn = hashi + i;
				hashn %= _sh.size();
				i++;
				if (hashn == hashi)
				{
					return pair<int, bool>(-1, false);
				}
			}
			return pair<int, bool>(-1, false);
		}

	private:
		vector<Date> _sh; //数据存储
		size_t _n = 0;       //存储数据的个数
	};
}


namespace HashBucket
{
	template<class K, class V>
	struct HashDate
	{
		HashDate(pair<K, V> kv)
			:_kv(kv),
			_next(nullptr)
		{}

		pair<K, V> _kv;
		HashDate* _next;
	};

	template<class K,class V>
	class Hashbucket
	{
		typedef HashDate<K, V> Date;
		typedef pair<K, V> KV;
	public:

		bool insert(KV kv)
		{
			//如果待插入的数据已经存在
			if ( find(kv.first))
			{
				return false;
			}

			//扩容
			if (_n == _table.size())
			{
				//计算新的桶数
				size_t newsize = _table.size() == 0 ? 10 : _table.size() * 2;
				//创建一个新的表
				vector<Date*> newtable;
				newtable.resize(newsize);
				//将就表中的结点拿过来,注意此处直接拿旧的结点插入新表,
				//而不是拿到数据后创建新的结点,减少不必要的消耗
				for (auto& cur : _table)
				{
					while (cur)
					{
						size_t hashi = cur->_kv.first % newtable.size();
						Date* next = cur->_next;
						cur->_next = newtable[hashi];
						newtable[hashi] = cur;
						cur = next;
					}
				}
				//新旧表交换
				_table.swap(newtable);
			}

			//插入
			size_t hashi = kv.first % _table.size();
			Date* newNode = new Date(kv);
			newNode->_next = _table[hashi];
			_table[hashi] = newNode;
			_n++;
		}

		Date* find(const K& key)
		{
			//如果表是空的,即就是没有一个数据
			if (_table.empty())
			{
				return nullptr;
			}
			//1.计算桶的位置
			size_t hashi = key % _table.size();
			Date* cur = _table[hashi];
			//2.在桶里面遍历查询
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			return nullptr;
		}

		bool erase(const K& key)
		{
			//1.查找在不在表中
			if (!find(key))
			{
				return false;
			}
			//2.计算桶的位置
			size_t hashi = key % _table.size();
			Date* cur = _table[hashi];
			//记录前驱结点
			Date* prov = nullptr;
			//3.找到待删除的结点
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					if (prov == nullptr)
					{
						_table[hashi] = cur->_next;	
					}
					else
					{
						prov->_next = cur->_next;
					}
					//4.删除改变指向,释放结点
					delete cur;
					return true;
				}
				else
				{
					prov = cur;
					cur = cur->_next;
				}
			}
		}
		~Hashbucket()
		{
			for (auto& cur : _table)
			{
				while (cur)
				{
					Date* next = cur->_next;
					delete cur;
					cur = next;
				}
				cur = nullptr;
			}
		}

	private:
		vector<Date*> _table;
		size_t _n = 0;
	};
}

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

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

相关文章

十四、pikachu之XSS

文章目录 1、XSS概述2、实战2.1 反射型XSS&#xff08;get&#xff09;2.2 反射型XSS&#xff08;POST型&#xff09;2.3 存储型XSS2.4 DOM型XSS2.5 DOM型XSS-X2.6 XSS之盲打2.7 XSS之过滤2.8 XSS之htmlspecialchars2.9 XSS之href输出2.10 XSS之JS输出 1、XSS概述 Cross-Site S…

基于Axios完成前后端分离项目数据交互

一、安装Axios npm i axios -S 封装一个请求工具&#xff1a;request.js import axios from axios// 创建可一个新的axios对象 const request axios.create({baseURL: http://localhost:9090, // 后端的接口地址 ip:porttimeout: 30000 })// request 拦截器 // 可以自请求…

Ansys Zemax | 手机镜头设计 - 第 2 部分:使用 OpticsBuilder 实现光机械封装

本文是3篇系列文章的一部分&#xff0c;该系列文章将讨论智能手机镜头模块设计的挑战&#xff0c;从概念、设计到制造和结构变形的分析。本文是三部分系列的第二部分。概括介绍了如何在 CAD 中编辑光学系统的光学元件以及如何在添加机械元件后使用 Zemax OpticsBuilder 分析系统…

Qt下载安装及配置教程

进入qt中文网站&#xff1a;https://www.qt.io/zh-cn/ 下载开源版 往下滑&#xff0c;下载Qt在线安装程序 它已经检测出我的是windows系统&#xff0c;直接点击download就好。如果是其它的系统&#xff0c;需要找到对应自己系统的安装包。 然后跟网速有关&#xff0c;等…

[Mac软件]Adobe After Effects 2023 v23.5 中文苹果电脑版(支持M1)

After Effects是动画图形和视觉效果的行业标准。由运动设计师、平面设计师和视频编辑用于创建复杂的动画图形和视觉上吸引人的视频。 创建动画图形 使用预设样式为文本和图形添加动画效果&#xff0c;或逐帧调整它们。编辑、添加深度、制作动画或转换为可编辑的路径&#xff…

Spring Boot整合RabbitMQ之路由模式(Direct)

RabbitMQ中的路由模式&#xff08;Direct模式&#xff09;应该是在实际工作中运用的比较多的一种模式了&#xff0c;这个模式和发布与订阅模式的区别在于路由模式需要有一个routingKey&#xff0c;在配置上&#xff0c;交换机类型需要注入DirectExchange类型的交换机bean对象。…

avalonia、WPF使用ScottPlot动态显示ECG心电图

文章目录 avalonia、WPF使用ScottPlot动态显示ECG心电图实现效果&#xff0c;动态效果懒得录视频了安装代码部分UpdateData方法就是用来更新心电图表的方法&#xff0c; 根据消息队列数据去更新是视图中的ScottPlot 图表 avalonia、WPF使用ScottPlot动态显示ECG心电图 avalonia…

【随笔】如何使用阿里云的OSS保存基础的服务器环境

使用阿里云OSS创建一个存储仓库&#xff1a;bucket 在Linux上下载并安装阿里云的ossutil工具 // 命令行&#xff0c;是linux环境 3. 安装ossutil。sudo -v ; curl https://gosspublic.alicdn.com/ossutil/install.sh | sudo bash 说明:安装过程中&#xff0c;需要使用解压工具…

Next.js基础语法

Next.js 目录结构 入口App组件&#xff08;_app.tsx&#xff09; _app.tsx是项目的入口组件&#xff0c;主要作用&#xff1a; 可以扩展自定义的布局&#xff08;Layout&#xff09;引入全局的样式文件引入Redux状态管理引入主题组件等等全局监听客户端路由的切换 ts.config…

什么是光流传感器

传感器 文章目录 传感器前言一、光流传感器二、px4FLOW 前言 光流利用的是图像的变化处理&#xff0c;用于检测地面的状态&#xff0c;从而监测飞机的移动&#xff1b;主要用于保持飞机的水平位置&#xff0c;以及在室内实现定高和定点飞行。 其实光流是数字图像处理理论的一部…

【MySQL】MySQL里的用户账户和角色是什么?如何管理?

用户&#xff08;user&#xff09;验证和授权创建用户账户连接服务器查看用户账户设置 角色&#xff08;role&#xff09;创建角色 操作用户帐户和角色重命名删除 感谢 &#x1f496; 用户&#xff08;user&#xff09; 在MySQL中&#xff0c;用户是数据库访问的主要实体。每个…

vscode vue3自定义自动补全

敲代码多了&#xff0c;发现重发动作很多&#xff0c;于是还是定义自动补全代码吧——懒是第一生产力&#xff01; 1&#xff0c;Ctrl Shift P打开快捷命令行&#xff1a;找到下面这个 2&#xff0c;然后找到ts&#xff1a; 里面给了demo照着写就行 // "Print to conso…

可拖动表格

支持行拖动&#xff0c;列拖动 插件&#xff1a;sortablejs UI: elementUI <template><div><hr style"margin: 30px 0;"><div><!-- 数据里面要有主键id&#xff0c; 否则拖拽异常 --><h2 style"margin-bottom: 30px&qu…

uniapp 项目实践总结(二)从零开始搭建一个项目

导语:本篇文章主要是项目方面的技术开发总结,新建一个项目可以选择使用可视化界面,也可以使用命令行搭建。 目录 可视化界面命令行搭建安卓开发环境苹果开发环境可视化界面 安装软件 使用官方推荐的 HbuilderX 软件,开发方式比较简单,内置相关环境以及终端,无需配置 no…

CSS按钮-跑马灯边框

思路很简单&#xff0c;实现方法有很多很多。但是大体思路与实现方法都类似&#xff1a;渐变色 动画&#xff0c;主要区别在动画的具体实现 0、HTML 结构 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><titl…

【uniapp 配置启动页面隐私弹窗】

为什么需要配置 原因 根据工业和信息化部关于开展APP侵害用户权益专项整治要求&#xff0c;App提交到应用市场必须满足以下条件&#xff1a; 1.应用启动运行时需弹出隐私政策协议&#xff0c;说明应用采集用户数据 2.应用不能强制要求用户授予权限&#xff0c;即不能“不给权…

[C/C++]天天酷跑游戏超详细教程-上篇

个人主页&#xff1a;北海 &#x1f390;CSDN新晋作者 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏✨收录专栏&#xff1a;C/C&#x1f91d;希望作者的文章能对你有所帮助&#xff0c;有不足的地方请在评论区留言指正&#xff0c;大家一起学习交流&#xff01;&#x1f9…

WebGL模型矩阵

前言&#xff1a;依赖矩阵库 WebGL矩阵变换库_山楂树の的博客-CSDN博客 先平移&#xff0c;后旋转的模型变换&#xff1a; 1.将三角形沿着X轴平移一段距离。 2.在此基础上&#xff0c;旋转三角形。 先写下第1条&#xff08;平移操作&#xff09;中的坐标方程式。 等式1&am…

2023年DAMA-CDGA/CDGP数据治理认证线上班到这里

DAMA认证为数据管理专业人士提供职业目标晋升规划&#xff0c;彰显了职业发展里程碑及发展阶梯定义&#xff0c;帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力&#xff0c;促进开展工作实践应用及实际问题解决&#xff0c;形成企业所需的新数字经济下的核心职业…

Fooocus:一个简单且功能强大的Stable Diffusion webUI

Stable Diffusion是一个强大的图像生成AI模型&#xff0c;但它通常需要大量调整和提示工程。Fooocus的目标是改变这种状况。 Fooocus的创始人Lvmin Zhang&#xff08;也是 ControlNet论文的作者&#xff09;将这个项目描述为对“Stable Diffusion”和“ Midjourney”设计的重新…