哈希的基本原理

目录

一.哈希概念

二.哈希冲突

三.哈希函数

四.哈希冲突解决

一.闭散列(开放寻址法)

①插入:

②查找:

③删除:

代码+测试:

二.开散列(拉链法)

①插入:

②查找:

③删除:

代码+测试:

五.开散列与闭散列比较


一.哈希概念

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

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

当向该结构中:

        插入元素:根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放;
        搜索元素:对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功。

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

例如:数据集合{1,7,5,6,9}

哈希函数设置为:hash(key) = key % capacity;    capacity为存储元素底层空间总的大小。

用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快

二.哈希冲突

对于两个数据元素的关键字 i 和 j (i != j),但有:Hash(i) == Hash(j),即:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突 或哈希碰撞. 把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。

例如把44插入上图中即与4发生冲突。

三.哈希函数

引起哈希冲突的一个原因可能是:哈希函数设计不够合理

哈希函数设计原则

  • 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有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种不同的符号在各位上出现的频率不          一定相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布          不均匀只有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的         若干位作为散列地址。
                       
注意:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突

四.哈希冲突解决

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

一.闭散列(开放寻址法)

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

①插入:

线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。

比如我们上面举的例子:现在我们需要插入44这个元素,先通过哈希函数计算哈希地址,hashAddr为4, 因此44理论上应该插在该位置,但是该位置已经放了值为4的元素,即发生哈希冲突。

1、通过哈希函数获取待插入元素在哈希表中的位置

2、如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素。

那么如果当前列表已经满了,再插入一个元素呢,很明显我们需要考虑扩容问题,我们引入:

散列表的载荷因子定义为: \alpha= 填入表中的元素个数 / 散列表的长度

\alpha是散列表装满程度的标志因子,由于表长是定值,与“填入表中的元素个数”成正比,越大填入表中元素越多,产生冲突的可能性越大;反之,越小填入表中的元素越少,产生冲突的可能性就越小;实际上,散列表的平均查找长度是载荷因子的函数,只是不同处理冲突的方法有不同的函数;

        对于开放定址法,载荷因子是特别重要因素,应严格限制在0.7-0.8以下;超过0.8,查表时cpu缓存不命中(cache missing)按照指数曲线上升,因此一些采用开放定址法的hash库,如Java的系统库限制了载荷因子为0.75,超过此值将resize散列表

//大于0.7  防止浮点数可以采用乘10大于7
if (_n * 10 / _tables.size() >= 7)
{

	HashTable<K, V, Hash> newHT;
	newHT._tables.resize(_tables.size() * 2);

	// 旧表重新计算负载到新表
	for (size_t i = 0; i < _tables.size(); i++)
	{
        //EXIST见下文删除部分
		if (_tables[i]._state == EXIST)
		{
			newHT.Insert(_tables[i]._kv);
		}
	}

	_tables.swap(newHT._tables);
}

那么如果我们插入的元素不是单纯的整形又该怎么办呢?我们应寻找一个处理方法:对于非字符串的元素,我们将其强转为size_t类型,对于字符串类型,我们使用131进制将其转为数字即可。

其过程还是用到了仿函数,模板将string特化一下即可。


template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

//特化string
template<>
struct HashFunc<string>
{
	size_t operator()(const string& key)
	{
		//131进制是科学家发现的冲突极少的进制
		//自然溢出即可 相当于mod操作
		size_t hash = 0;
		for (const auto& ch : key)
		{
			hash *= 131;
			hash += ch;
		}
		return hash;
	}
};

插入总代码:


bool Insert(const pair<K, V>& kv)
{
	if (Find(kv.first))
		return false;
	//exit(0);
	// 扩容
	if (_n * 10 / _tables.size() >= 7)
	{
		//size_t newsize = _tables.size() * 2;
		//vector<HashData<K, V>> newtables(newsize);

		 旧表重新计算负载到新表
		//for (size_t i = 0; i < _tables.size(); i++)
		//{}
		HashTable<K, V, Hash> newHT;
		newHT._tables.resize(_tables.size() * 2);

		// 旧表重新计算负载到新表
		for (size_t i = 0; i < _tables.size(); i++)
		{
			if (_tables[i]._state == EXIST)
			{
				newHT.Insert(_tables[i]._kv);
			}
		}

		_tables.swap(newHT._tables);
	}

	Hash hs;
	size_t hashi = hs(kv.first) % _tables.size();

	// 线性探测
	while (_tables[hashi]._state == EXIST)
	{
		++hashi;
		hashi %= _tables.size();
	}

	_tables[hashi]._kv = kv;
	_tables[hashi]._state = EXIST;
	++_n;


	return true;
}

线性探测优点:实现非常简单。

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

二次探测:

哈希冲突后,找下一个空位置的方法为

(i=1,2,3...,是通过散列函数Hash(X)对元素的关键码key进行计算得到的位置,m表示表的大小)

即若一个位置x冲突,则去看x + 1 和 x - 1. 若没有空位置再看 x + 4 和 x - 4,以此类推。

研究表明,当表长度为质数且载荷因子不超过0.5时,新表项一定能够插入,而且任何一个位置都不会被探查两次,因此只要表中有一半的位置,就不会存在表满的问题,在搜索时可以不考虑表装满的情况,但在插入时必须确保表的载荷因子不超过0.5,如超过需考虑增容;

代码作者不再展示。

②查找:

找到映射位置后,若不是对应元素,线性往后找即可。

HashData<K, V>* Find(const K& key)
{
	Hash hs;
	size_t hashi = hs(key) % _tables.size();

	//不可能全是满的 负载因子会更新数组大小
	while (_tables[hashi]._state != EMPTY)
	{
		if (_tables[hashi]._state == EXIST &&
			_tables[hashi]._kv.first == key)
		{
			return &_tables[hashi];
		}

		++hashi;
		hashi %= _tables.size();
	}

	return nullptr;
}

③删除:

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

//伪标记
enum State
{
	EMPTY,
	EXIST,
	DELETE
};
bool Erase(const K& key)
{
	HashData<K, V>* ret = Find(key);

	if (ret == nullptr)
	{
		return false;
	}
	else
	{
		ret->_state = DELETE;
		_n--;
		return true;
	}
}

代码+测试:

#include<vector>
#include<string>

template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

//特化string
template<>
struct HashFunc<string>
{
	size_t operator()(const string& key)
	{
		//131进制是科学家发现的冲突极少的进制
		//自然溢出即可 相当于mod操作
		size_t hash = 0;
		for (const auto& ch : key)
		{
			hash *= 131;
			hash += ch;
		}
		return hash;
	}
};


// 开放寻址法
namespace open_address
{

	enum State
	{
		EMPTY,
		EXIST,
		DELETE
	};

	template<class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		State _state = EMPTY;
	};

	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{

	public:
		HashTable()
		{
			//初始化大小
			_tables.resize(10);
		}

		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
				return false;
			//exit(0);
			// 扩容
			if (_n * 10 / _tables.size() >= 7)
			{
				//size_t newsize = _tables.size() * 2;
				//vector<HashData<K, V>> newtables(newsize);

				 旧表重新计算负载到新表
				//for (size_t i = 0; i < _tables.size(); i++)
				//{}
				HashTable<K, V, Hash> newHT;
				newHT._tables.resize(_tables.size() * 2);

				// 旧表重新计算负载到新表
				for (size_t i = 0; i < _tables.size(); i++)
				{
					if (_tables[i]._state == EXIST)
					{
						newHT.Insert(_tables[i]._kv);
					}
				}

				_tables.swap(newHT._tables);
			}

			Hash hs;
			size_t hashi = hs(kv.first) % _tables.size();

			// 线性探测
			while (_tables[hashi]._state == EXIST)
			{
				++hashi;
				hashi %= _tables.size();
			}

			_tables[hashi]._kv = kv;
			_tables[hashi]._state = EXIST;
			++_n;
			

			return true;
		}

		HashData<K, V>* Find(const K& key)
		{
			Hash hs;
			size_t hashi = hs(key) %_tables.size();

			//不可能全是满的 负载因子会更新数组大小
			while (_tables[hashi]._state != EMPTY)
			{
				if (_tables[hashi]._state == EXIST &&
					_tables[hashi]._kv.first == key)
				{
					return &_tables[hashi];
				}

				++hashi;
				hashi %= _tables.size();
			}

			return nullptr;
		}

		bool Erase(const K& key)
		{
			HashData<K, V>* ret = Find(key);

			if (ret == nullptr)
			{
				return false;
			}
			else
			{
				ret->_state = DELETE;
				_n--;
				return true;
			}
		}

	private:
		vector<HashData<K, V>> _tables;
		size_t _n = 0; //有效数据个数
	};


	void TestHT1()
	{
		int a[] = { 10001,11,55,24,19,12,31 };

		HashTable<int, int> ht;

		for (auto e : a)
		{

			ht.Insert(make_pair(e, e));
		}

		
		cout << ht.Find(55) << endl;
		cout << ht.Find(31) << endl;

		ht.Erase(55);
		cout << ht.Find(55) << endl;
		cout << ht.Find(31) << endl;
	}

	void TestHT2()
	{
		int a[] = { 10001,11,55,24,19,12,31 };
		HashTable<int, int> ht;
		for (auto e : a)
		{
			ht.Insert(make_pair(e, e));
		}

		ht.Insert(make_pair(32, 32));
		ht.Insert(make_pair(32, 32));
	}

	struct Person
	{
		//string _id;

		string _name;
		int _age;
		string school;
	};

	// 21:15

	// key不支持强转整形取模,那么就要自己提供转换成整形仿函数
	//void TestHT3()
	//{
	//	HashTable<Person, int> xxht;

	//	//HashTable<string, int, StringHashFunc> ht;
	//	HashTable<string, int> ht;
	//	ht.Insert(make_pair("sort", 1));
	//	ht.Insert(make_pair("left", 1));
	//	ht.Insert(make_pair("insert", 1));

	//	cout << StringHashFunc()("bacd") << endl;
	//	cout << StringHashFunc()("abcd") << endl;
	//	cout << StringHashFunc()("aadd") << endl;
	//}

	void test_map1()
	{
		string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
	"苹果", "香蕉", "苹果", "香蕉","苹果","草莓", "苹果","草莓" };
		unordered_map<string, int> countMap;
		for (auto& e : arr)
		{
			countMap[e]++;
		}

		cout << countMap.load_factor() << endl;
		cout << countMap.max_load_factor() << endl;
		cout << countMap.size() << endl;
		cout << countMap.bucket_count() << endl;
		cout << countMap.max_bucket_count() << endl;

		for (auto& kv : countMap)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
		cout << endl;
	}
}

因此:闭散列最大的缺陷就是空间利用率比较低,这也是哈希的缺陷。

二.开散列(拉链法)

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

两者相似处较多,重复部分不再赘述

①插入:

开散列为单链表插入,采用相对简单的头插。

对于扩容,开散列最好的情况是:每个哈希桶中刚好挂一个节点, 再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,可以给哈希表增容,即载荷因子等于 1时。

bool Insert(const pair<K, V>& kv)
{
	if (Find(kv.first))
		return false;
	//exit(0);
	// 扩容
	if (_n * 10 / _tables.size() >= 7)
	{
		//size_t newsize = _tables.size() * 2;
		//vector<HashData<K, V>> newtables(newsize);

		 旧表重新计算负载到新表
		//for (size_t i = 0; i < _tables.size(); i++)
		//{}
		HashTable<K, V, Hash> newHT;
		newHT._tables.resize(_tables.size() * 2);

		// 旧表重新计算负载到新表
		for (size_t i = 0; i < _tables.size(); i++)
		{
			if (_tables[i]._state == EXIST)
			{
				newHT.Insert(_tables[i]._kv);
			}
		}

		_tables.swap(newHT._tables);
	}

	Hash hs;
	size_t hashi = hs(kv.first) % _tables.size();

	// 线性探测
	while (_tables[hashi]._state == EXIST)
	{
		++hashi;
		hashi %= _tables.size();
	}

	_tables[hashi]._kv = kv;
	_tables[hashi]._state = EXIST;
	++_n;


	return true;
}

②查找:

找到映射位置链表查询即可

Node* Find(const K& key)
{
	size_t hashi = key % _tables.size();
	Node* cur = _tables[hashi];
	while (cur)
	{
		if (cur->_kv.first == key)
		{
			return cur;
		}

		cur = cur->_next;
	}
}

③删除:

单链表的删除

bool Erase(const K& key)
{
	size_t hashi = key % _tables.size();
	Node* prev = nullptr;
	Node* cur = _tables[hashi];

	while (cur)
	{
		if (cur->_kv.first == key)
		{
			// 删除的是第一个
			if (prev == nullptr)
			{
				_tables[hashi] = cur->_next;
			}
			else
			{
				prev->_next = cur->_next;
			}
			return true;
		}
		else
		{
			prev = cur;
			cur = cur->_next;
		}
	}
	return false;
}

代码+测试:

// 哈希桶(拉链法)
namespace hash_bucket
{
	template<class K, class V>
	struct HashNode
	{
		pair<K, V> _kv;
		HashNode<K, V>* _next;
	};

	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;
	public:

		HashTable()
		{
			_tables.resize(10);
		}

		bool Insert(const pair<K, V>& kv)
		{
			Hash hs;
			//负载因子为1时扩容
			if (_n == _tables.size())
			{
				vector<Node*> newTables(_tables.size() * 2, nullptr);
				for (int i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					//给每条链的每一个值重定向
					while (cur)
					{
						Node* next = cur->_next;

						size_t hashi = hs(cur->_kv.first) % _tables.size();
						cur->_next = _tables[hashi];
						_tables[hashi] = cur;

						cur = next;
					}
					_tables.swap(newTables);
				}

				size_t hashi = hs(kv.first) % _tables.size();
				Node* newnode = new Node(kv);

				//头插
				newnode->_next = _tables[hashi];
				_tables[hashi] = newnode;

				return true;
			}
		}

		Node* Find(const K& key)
		{
			size_t hashi = key % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}

				cur = cur->_next;
			}
		}

		bool Erase(const K& key)
		{
			size_t hashi = key % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];

			while (cur)
			{
				if (cur->_kv.first == key)
				{
					// 删除的是第一个
					if (prev == nullptr)
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					return true;
				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			return false;
		}

	private:
		vector<Node*> _tables;
		size_t _n;
	};
}

五.开散列与闭散列比较

应用链地址法处理溢出,需要增设链接指针,似乎增加了存储开销。事实上: 由于开地址法必须保持大量的空闲空间以确保搜索效率,如线性探查法要求装载因子a <= 0.7,而表项所占空间又比指针大的多,所以使用链地址法反而比开地址法节省存储空间。

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

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

相关文章

推荐一个Python的前端框架Streamlit

WHY&#xff0c;为什么要用Streamlit 你是不是也想写一个简单的前端界面做些简单的展示和控制&#xff0c;不想写html、css、js&#xff0c;也用不到前后端分离&#xff0c;用不到特别复杂的Flask、Django等&#xff0c;如果你遇到类似这样的问题&#xff0c;我推荐你试试Stre…

LSM-Tree数据结构原理

LSM-Tree树原理 什么是LSM-Tree LSM-Tree 即 Log Structrued Merge Tree&#xff0c;这是一种分层有序&#xff0c;硬盘友好的数据结构。核心思想是利用磁盘顺序写性能远高于随机写。 LSM-Tree 并不是一种严格的树结构&#xff0c;而是一种内存磁盘的多层存储结构。HBase、L…

c++中string的用法

STL的简介 一.什么是STL二.STL的六大组件2.1仿函数2.2空间配置器2.3 算法2.4 迭代器2.5容器2.6配置器 三.string类3.1string类3.2string类的常用接口说明代码示例运行结果 3.3string类对象的容量操作代码示例sizelengthcapcityempty resizereverse 3.4string类对象的访问及遍历…

LVGL开发教程-按钮Button

系列文章目录 知不足而奋进 望远山而前行 目录 系列文章目录 文章目录 前言 1. 普通Button 2.可选中Button 3.按钮事件处理 总结 前言 在图形用户界面&#xff08;GUI&#xff09;开发中&#xff0c;按钮&#xff08;Button&#xff09;是用户与程序交互的重要组件之一…

目标检测数据集 - PCB板表面缺陷检测数据集下载「包含VOC、COCO、YOLO三种格式」

数据集介绍&#xff1a;PCB 板表面缺陷检测数据集&#xff0c;真实采集高质量 PCB 板表面含缺陷图片数据&#xff0c;数据集含多款不同 PCB 板高清表面图片数据&#xff0c;包括俯拍正拍、旋转拍摄姿态。数据标注标签包括 missing_hole、mouse_bite、open_circuit、short、spur…

6.17继承

面向对象的特征&#xff1a;封装&#xff0c;继承&#xff0c;多态 使用背景&#xff1a;比如说在动物类底下可以有带毛的动物&#xff0c;带毛的动物符合所有的动物的特征&#xff0c;只是在这个基础上再继续添加一些特征 命名&#xff1a;原有类型称为“基类”或“父类”&a…

Springboot集成Mybatisplus过程

这里写目录标题 背景步骤明确标准实操过程创建好数据库&#xff0c;命名好&#xff08;这里会考察一个命名规范&#xff09;&#xff0c;表的命名&#xff0c;中间使用下划线隔离开。使用idea创建Springboot项目&#xff08;注意版本问题&#xff09;使用插件生成代码常用代码p…

Nuxt3 实战 (十):使用 Supabase 实现 RESTful 风格 API 接口

前言 本篇文章我们来使用 Supabase 实现 RESTful 风格的 API 接口&#xff0c;以此来实现网站分类和子站点的 CURD 功能。 表设计 这里需要用到两张表&#xff1a; ds_categorys&#xff1a;存储网站分类 列名类型备注iduuid主键&#xff0c;分类 idnametext分类名称desct…

重生之 SpringBoot3 入门保姆级学习(22、场景整合 Swagger 接口文档)

重生之 SpringBoot3 入门保姆级学习&#xff08;22、场景整合 Swagger 接口文档&#xff09; 6.2 Swagger 接口文档 6.2 Swagger 接口文档 1、将 starter 导入 Maven 官网 https://springdoc.org/<dependency><groupId>org.springdoc</groupId><artifact…

学习记录之数学表达式(5)

文章目录 十、线性回归10.1 示例10.2 拟合10.3 推导10.4 岭回归10.5 作业 十一、Logistic回归11.1 分割超平面11.2 点到直线的距离11.3 sigmoid函数11.4 优化目标11.5 求解11.6 作业 十、线性回归 线性回归是一个常用的机器学习算法&#xff1b; 10.1 示例 表 1.单变量的股价预…

格雷母线技术革新:推动斗轮堆取料机进入精准操作时代

随着工业4.0时代的到来&#xff0c;智能化、自动化已成为工业发展的必然趋势。特别是在港口、电力、冶金等行业中&#xff0c;散料装卸机械的智能化水平直接关系到整个生产流程的效率与安全。斗轮堆取料机作为这些行业中的关键设备&#xff0c;其操作方式的革新显得尤为重要。 …

Unity OpenCVForUnity 安装和第二个案例详解 <二>

目录 一、前言 二、场景介绍 1.WebCamTextureToMatExample脚本 2.FpsMonitor脚本 三、 结构体Scaler 四、找到相机并使用 1.相机的启用 2.格式转换 a.把webCamTexture转换成Mat b.把Mat转换成Texture2D 五、脚本组合 六、作者的碎碎念 一、前言 第二个案例&#xf…

leetcode (top100)盛最多水的容器

题目&#xff1a; 题解&#xff1a; 第一种可行的方案&#xff1a; 设置左指针指向第一条线&#xff0c;设置右指针指向最后一条线。每次向中间移动两条线中最短的一条&#xff0c;计算移动过程中最大接水量。 本题可以看出影响接水量的有两个因素&#xff0c;两条线的距离&…

空间复杂度的相关概念

1. 空间复杂度 空间复杂度&#xff08;space complexity&#xff09;用于衡量算法占用内存空间随着数据量变大时的增长趋势。 统计哪些空间&#xff1a; ● 暂存数据&#xff1a;用于保存算法运行过程中的各种常量、变量、对象等。 ● 栈帧空间&#xff1a;用于保存调用函数…

PyTorch -- RNN 快速实践

RNN Layer torch.nn.RNN(input_size,hidden_size,num_layers,batch_first) input_size: 输入的编码维度hidden_size: 隐含层的维数num_layers: 隐含层的层数batch_first: True 指定输入的参数顺序为&#xff1a; x&#xff1a;[batch, seq_len, input_size]h0&#xff1a;[batc…

Ubuntu 24.04安装zabbix7.0.0图形中文乱码

当zabbix安装完成后&#xff0c;设置中文界面时&#xff0c;打开图形&#xff0c;中文内容会显示方框乱码&#xff0c;是因为服务器字体中没有相关的中文字体&#xff0c;需要更换。 1、找到中文字体&#xff0c;可以在网络上下载《得意黑》开源字体&#xff0c;也可以在windo…

LeetCode322.零钱兑换(一)

LeetCode刷题记录 文章目录 &#x1f4dc;题目描述&#x1f4a1;解题思路⌨C代码 &#x1f4dc;题目描述 给你一个整数数组 coins &#xff0c;表示不同面额的硬币&#xff1b;以及一个整数 amount &#xff0c;表示总金额。 计算并返回可以凑成总金额所需的 最少的硬币个数 。…

SAP MIGO 050 BADI:字段 GOITEM-XXXXX 未准备好输出

背景&#xff1a; MIGO过账时候需要根据某些条件更改某些字段的值&#xff0c;当要改的字段在前台不显示时&#xff0c;通过MB_MIGO_BADI~LINE_MODIFY去更改时&#xff0c;则会出现以下报错&#xff1a;MIGO050 解决方案1&#xff1a; 通过配置将该字段配置显示出来即可&…

阿里云如何部署项目【2024 详细版】

首次注册阿里云后可以购买免费服务器&#xff0c;可以用服务器练习部署项目&#xff0c;这里以部署个人网站为例 本人目前没有购买域名&#xff0c;因此域名流程并没有写&#xff0c;有看不懂的私信或者评论就行&#xff0c;我都可以看见 目录 一、购买服务器 二、安装宝塔…

「Python-docx 专栏」docx设置罗马数字页码,即页码编码格式为罗马数字

本文目录 前言一、docx 设置罗马数字页码1、docx设置大写罗马数字的页码①、docx背后的xml长啥样②、<w:sectPr> 标签详解③、通过<w:sectPr> 设置大写罗马数字的页码A、完整代码B、处理效果图C、这段代码实际上的作用2、docx设置小写罗马数字的页码①、完整代码②…