哈希

news2025/1/18 17:07:21

在这里插入图片描述

欢迎来到Cefler的博客😁
🕌博客主页:那个传说中的man的主页
🏠个人专栏:题目解析
🌎推荐文章:题目大解析(3)

在这里插入图片描述


目录

  • 👉🏻unordered系列关联式容器
    • unordered_map
    • unordered_set
  • 👉🏻哈希概念
  • 👉🏻哈希函数
  • 👉🏻哈希冲突
  • 👉🏻 哈希冲突解决方法
    • 闭散列
      • 线性探测
  • 👉🏻哈希闭散列模拟实现
    • 哈希数据和哈希状态
    • 返回哈希值的key
    • 哈希插入(含扩容和线性探测)
    • 哈希查找
    • 哈希的伪删除
    • 哈希打印
    • 哈希闭散列完整代码附测试代码
  • 👉🏻哈希开散列(哈希桶)模拟实现
    • 概念
    • 哈希节点
    • 哈希插入(含扩容)
    • 析构函数
    • 返回哈希值的key
    • 哈希查找
    • 哈希删除
    • 显示哈希的一些情况(桶的数量、长度等)
    • 哈希开散列完整代码附测试代码

👉🏻unordered系列关联式容器

在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到 l o g 2 N log_2 N log2N,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同(unordered系列底层是哈希表)

unordered_map

unordered_map官方文档: 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. 它的迭代器至少是前向迭代器

unordered_set

unordered_set官方文档:unordered_set

1.无序集合是存储没有特定顺序的唯一元素的容器,它允许基于它们的值快速检索单个元素。

2.在unordered_set中,元素的值同时也是唯一标识它的键。键是不可变的,因此,在容器中不能修改unordered_set中的元素——但是可以插入和删除它们。

3.在内部,unordered_set中的元素没有按照任何特定的顺序排序,而是根据它们的散列值组织到bucket中,以便通过它们的值直接快速访问单个元素(平均时间复杂度为常数)。

4.Unordered_set容器在按键访问单个元素时比set容器快,尽管它们在通过其元素子集进行范围迭代时通常效率较低。

5.容器中的迭代器至少是前向迭代器。

👉🏻哈希概念

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

理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素

如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。
当向该结构中:

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

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

  • 哈希/散列:映射,关键字和另一个值建立一个关联关系

  • 哈希表/散列表:映射,关键字和存储位置建立一个关联关系


👉🏻哈希函数

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

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

☃️常见的哈希函数有

  1. 直接定址法–(常用)
    取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B
    优点:简单、均匀,关键字—存储位置是一对一的关系,不存在哈希冲突
    缺点:需要事先知道关键字的分布情况
    使用场景:关键字范围集中,量不大的情况

  2. 除留余数法–(常用)
    设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,
    按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址
    其关键字-存储位置是多对一的关系,多个关键字对应一个位置,存在哈希冲突
    使用场景:关键字可以很分散,量可以很大


例如该下面这个就是采取除留余数法

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

在这里插入图片描述
用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快
问题:按照上述哈希方式,向集合中插入元素44,会出现什么问题?

👉🏻哈希冲突

🧋 概念 概念 概念
对于两个数据元素的关键字 k i k_i ki k j k_j kj(i != j),有 k i k_i ki != k j k_j kj,但有:Hash( k i k_i ki) == Hash( k j k_j kj),即:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突哈希碰撞

把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”
发生哈希冲突该如何处理呢? 🤔

👉🏻 哈希冲突解决方法

闭散列

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

线性探测

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

即线性探测的插入和删除表现为:

  • 插入

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

  • 删除

采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。比如删除元素4,如果直接删除掉,44查找起来可能会受影响,因为我们查找到4时,此时4的位置为空,我们就停下来了不往后找,自然就找不到44了。因此线性探测采用标记的伪删除法(标记状态为DELETE)来删除一个元素。

👉🏻哈希闭散列模拟实现

哈希数据和哈希状态

enum Status
	{
		EMPTY,
		EXIST,
		DELETE

	};
	template <class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		Status _s;
	};

返回哈希值的key

1.常规类型返回:如int、double等

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

2.字符串类型返回
这个就不能直接返回字符串,因为字符串是不能用于取余的,但是我们可以将字符串中的每一个字符的ASCII码值全部相加以整型的形式返回即可。
但是我们知道字符串的组合是无数种的,可size_t的值却是有上限的,那么多字符串最终会有很多字符串相加的ASCII码值会相等,这就会发生哈希冲突。对于哈希冲突,我们不可避免,但是在这个基础上,有算法大佬提供了牛逼的算法,可以减少这之间的哈希冲突的产生。
以下是一个著名的BRDK算法
在这里插入图片描述
我们借鉴这个算法进行将字符串转换为整型返回

template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& key)
		{
			size_t hash = 0;
			for (auto e : key)
			{
				hash = hash * 31 + e;
			}
			return hash;
		}
	};

我们观察到针对于这两种情况,我们都是采用了模板进行实例化,而字符串类型返回比较特殊这边用了全特化。
有人可能会问,为什么不把写成类的函数重载,而是用模板特化?
答案是这里会出现坑,当我们进行函数重载时,如果传进来的K是string,此时类中就会有两个size_t operator()(const string& key),发生冲突。
而特化就可以解决这个问题。

哈希插入(含扩容和线性探测)

🌊扩容

//如果空间不够?
			//负载因子0.7就扩容
			if (_n * 10 / _tables.capacity() == 7)
			{
				//不能原地扩容,因为原地扩容会使映射关系发生变化,比如size(10)->size(20),原本3的位置会变到13去
				//所以我们开辟新空间
				size_t newSize = _tables.size() * 2;
				HashTable<K, V,Hash> newHT;
				newHT._tables.resize(newSize);
				//遍历旧表,将旧表的值插入到新表中
				for (int i = 0; i < _tables.size(); i++)
				{
					if (_tables[i]._s == EXIST)
					{
						newHT.Insert(_tables[i]._kv);
					}
				}
				//最后旧表变新表
				_tables.swap(newHT._tables);

			}

🌊线性探测:

Hash hf;//HashFunc
			size_t hashi = hf(kv.first) % _tables.size();//size_t可以解决负数问题,负数隐式类型转换为无符号
			while (_tables[hashi]._s== EXIST) {
			  //存在则找下一个空位置
				hashi++;

				hashi %= _tables.size();
			}
			_tables[hashi]._kv = kv;
			_tables[hashi]._s = EXIST;
			++_n;
			return true;

整体插入代码

bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
				return false;
			//如果空间不够?
			//负载因子0.7就扩容
			if (_n * 10 / _tables.capacity() == 7)
			{
				//不能原地扩容,因为原地扩容会使映射关系发生变化,比如size(10)->size(20),原本3的位置会变到13去
				//所以我们开辟新空间
				size_t newSize = _tables.size() * 2;
				HashTable<K, V,Hash> newHT;
				newHT._tables.resize(newSize);
				//遍历旧表,将旧表的值插入到新表中
				for (int i = 0; i < _tables.size(); i++)
				{
					if (_tables[i]._s == EXIST)
					{
						newHT.Insert(_tables[i]._kv);
					}
				}
				//最后旧表变新表
				_tables.swap(newHT._tables);

			}
			//线性探测
			Hash hf;//HashFunc
			size_t hashi = hf(kv.first) % _tables.size();//size_t可以解决负数问题,负数隐式类型转换为无符号
			while (_tables[hashi]._s== EXIST) {
			  //存在则找下一个空位置
				hashi++;

				hashi %= _tables.size();
			}
			_tables[hashi]._kv = kv;
			_tables[hashi]._s = EXIST;
			++_n;
			return true;
		}

哈希查找

//哈希查找
		HashData<K, V>* Find(const K& key)
		{
			Hash hf;
			size_t hashi = hf(key) % _tables.size();
			while (_tables[hashi]._s != EMPTY)
			{
				if (_tables[hashi]._s == EXIST && _tables[hashi]._kv.first == key)
				{
					return &_tables[hashi];
				}
				++hashi;
				hashi %= _tables.size();
			}
			return nullptr;
		}

哈希的伪删除

伪删除就很简单,直接找到对应的位置,将该位置的状态改为DELETE,然后_n减一即可。

//伪删除
		bool Erase(const K& key)
		{
			HashData<K, V>* ret = Find(key);
			if (ret)
			{
				ret->_s = DELETE;
				_n--;
				return true;
			}
			else
				return false;
		}

哈希打印

//打印
		void Print()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				if (_tables[i]._s == EXIST)
				{
					//printf("[%d]->%d\n", i, _tables[i]._kv.first);
					cout << "[" << i << "]->" << _tables[i]._kv.first << ":" << _tables[i]._kv.second << endl;
				}
				else if (_tables[i]._s == EMPTY)
				{
					printf("[%d]->\n", i);
				}
				else
				{
					printf("[%d]->D\n", i);
				}
			}

			cout << endl;
		}

哈希闭散列完整代码附测试代码

#pragma once
#include<iostream>
#include<vector>
using namespace std;
namespace close_address
{
	enum Status
	{
		EMPTY,
		EXIST,
		DELETE

	};
	template <class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		Status _s;
	};
	//返回哈希的key
	//1.常规类型
	template <class  K>
	struct HashFunc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};
	//2.字符串转换为整型返回
	template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& key)
		{
			size_t hash = 0;
			for (auto e : key)
			{
				hash = hash * 31 + e;
			}
			return hash;
		}
	};
	template <class K,class V,class Hash = HashFunc<K>>
	class HashTable
	{
	public:
		HashTable()
		{
			_tables.resize(10);//一开始扩容为10
		}
		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
				return false;
			//如果空间不够?
			//负载因子0.7就扩容
			if (_n * 10 / _tables.capacity() == 7)
			{
				//不能原地扩容,因为原地扩容会使映射关系发生变化,比如size(10)->size(20),原本3的位置会变到13去
				//所以我们开辟新空间
				size_t newSize = _tables.size() * 2;
				HashTable<K, V,Hash> newHT;
				newHT._tables.resize(newSize);
				//遍历旧表,将旧表的值插入到新表中
				for (int i = 0; i < _tables.size(); i++)
				{
					if (_tables[i]._s == EXIST)
					{
						newHT.Insert(_tables[i]._kv);
					}
				}
				//最后旧表变新表
				_tables.swap(newHT._tables);

			}
			//线性探测
			Hash hf;//HashFunc
			size_t hashi = hf(kv.first) % _tables.size();//size_t可以解决负数问题,负数隐式类型转换为无符号
			while (_tables[hashi]._s== EXIST) {
			  //存在则找下一个空位置
				hashi++;

				hashi %= _tables.size();
			}
			_tables[hashi]._kv = kv;
			_tables[hashi]._s = EXIST;
			++_n;
			return true;
		}

		//哈希查找
		HashData<K, V>* Find(const K& key)
		{
			Hash hf;
			size_t hashi = hf(key) % _tables.size();
			while (_tables[hashi]._s != EMPTY)
			{
				if (_tables[hashi]._s == 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)
			{
				ret->_s = DELETE;
				_n--;
				return true;
			}
			else
				return false;
		}
		//打印
		void Print()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				if (_tables[i]._s == EXIST)
				{
					//printf("[%d]->%d\n", i, _tables[i]._kv.first);
					cout << "[" << i << "]->" << _tables[i]._kv.first << ":" << _tables[i]._kv.second << endl;
				}
				else if (_tables[i]._s == EMPTY)
				{
					printf("[%d]->\n", i);
				}
				else
				{
					printf("[%d]->D\n", i);
				}
			}

			cout << endl;
		}
	private:
		vector<HashData<K,V>> _tables;
		size_t _n = 0;//存储关键字的个数
	};
	void TestHT1()
	{
		HashTable<int, int> ht;
		int a[] = { 4,14,24,34,5,7,1 };
		for (auto e : a)
		{
			ht.Insert(make_pair(e, e));
		}

		ht.Print();
	}
	void TestHT2()
	{
		string arr[] = { "香蕉", "甜瓜","苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
		//HashTable<string, int, HashFuncString> ht;
		HashTable<string, int> ht;
		for (auto& e : arr)
		{
			//auto ret = ht.Find(e);
			HashData<string, int>* ret = ht.Find(e);
			if (ret)
			{
				ret->_kv.second++;
			}
			else
			{
				ht.Insert(make_pair(e, 1));
			}
		}

		ht.Print();

		ht.Insert(make_pair("apple", 1));
		ht.Insert(make_pair("sort", 1));

		ht.Insert(make_pair("abc", 1));
		ht.Insert(make_pair("acb", 1));
		ht.Insert(make_pair("aad", 1));

		ht.Print();
	}
}

👉🏻哈希开散列(哈希桶)模拟实现

概念

开放寻址法(Open Addressing)是一种解决哈希冲突的方法。在开放寻址法中,当发生冲突时,会通过一定的探测序列(如线性探测、二次探测等)在哈希表中的其他位置继续寻找空闲槽来存储冲突的元素。

开散列(Open Hashing),也被称为链地址法(Chaining),是另一种解决哈希冲突的方法。在开散列中,具有相同地
址的关键码归于同一子集合,每一个子集合称为一个桶
,当发生哈希冲突时,冲突的元素会被链接成一个链表或其他数据结构存储在同一个桶中。

在这里插入图片描述

哈希节点

template<class K,class V>
	struct HashNode
	{
		HashNode<K, V>* _next;
		pair<K, V> _kv;

		HashNode(const HashNode<K,V>& kv)
			:_kv(kv._kv)
			,_next(nullptr)
		{}

	};

哈希插入(含扩容)

bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
				return false;
			//扩容,这里负载因子可以最大到1
			if (_n == _tables.size())
			{
				size_t newSize() = _tables.size() * 2;
				HashTable<K,V> newHT;
				newHT._tables.resize(newSize);
				//遍历旧表,插入新表
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						newHT.Insert(cur->_kv);
						cur = cur->_next;
					}
				}
				_tables.swap(newHT._tables);
			
			}
			//插入的新节点头插
			Hash hf;
			size_t hashi = hf(kv.first) % _tables.size();
			Node* newnode = new Node(kv);

			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			_n++;
			return true;
		}

扩容方法2:

if (_n == _tables.size())
			{
			

				vector<Node*> newTables;
				newTables.resize(_tables.size() * 2, nullptr);
				// 遍历旧表
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while(cur)
					{
						Node* next = cur->_next;

						// 挪动到映射的新表
						size_t hashi = hf(cur->_kv.first) % newTables.size();
						cur->_next = newTables[i];
						newTables[i] = cur;

						cur = next;
					}

					_tables[i] = nullptr;
				}

				_tables.swap(newTables);
			}

析构函数

~HashTable()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_tables[i] = nullptr;
			}
		}

返回哈希值的key

//返回哈希的key
	//1.常规类型
	template <class  K>
	struct HashFunc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};
	//2.字符串转换为整型返回
	template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& key)
		{
			size_t hash = 0;
			for (auto e : key)
			{
				hash = hash * 31 + e;
			}
			return hash;
		}
	};

哈希查找

//哈希查找
		Node* Find(const K& key)
		{
			Hash hf;
			size_t hashi = hf(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_next == key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			return nullptr;
		}

哈希删除

//哈希删除
		bool Erase(const K& key)
		{
			Hash hf;
			size_t hashi = hf(key) % _tables.size();
			Node* cur = _tables[hashi];
			Node* prev = nullptr;
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					if (prev == nullptr)
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					return true ;
				}
				prev = cur;
				cur = cur->_next;
				
			}
			return false;
		}

显示哈希的一些情况(桶的数量、长度等)

void Some()
		{
			size_t bucketSize = 0;
			size_t maxBucketLen = 0;
			size_t sum = 0;
			double averageBucketLen = 0;

			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				if (cur)
				{
					++bucketSize;
				}

				size_t bucketLen = 0;
				while (cur)
				{
					++bucketLen;
					cur = cur->_next;
				}

				sum += bucketLen;

				if (bucketLen > maxBucketLen)
				{
					maxBucketLen = bucketLen;
				}
			}

			averageBucketLen = (double)sum / (double)bucketSize;

			printf("all bucketSize:%d\n", _tables.size());
			printf("bucketSize:%d\n", bucketSize);
			printf("maxBucketLen:%d\n", maxBucketLen);
			printf("averageBucketLen:%lf\n\n", averageBucketLen);
		}

哈希开散列完整代码附测试代码

#pragma once
#include<iostream>
#include<vector>
#include<unordered_set>
#include<set>
using namespace std;
namespace hash_bucket
{
	template<class K,class V>
	struct HashNode
	{
		HashNode<K, V>* _next;
		pair<K, V> _kv;

		HashNode(const pair<K,V>& kv)
			:_kv(kv)
			,_next(nullptr)
		{}

	};
	//返回哈希的key
	//1.常规类型
	template <class  K>
	struct HashFunc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};
	//2.字符串转换为整型返回
	template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& key)
		{
			size_t hash = 0;
			for (auto e : key)
			{
				hash = hash * 31 + e;
			}
			return hash;
		}
	};
	template<class K,class V,class Hash = HashFunc<K>>
	class HashTable
	{
	public:
		typedef HashNode<K, V> Node;
		HashTable()
		{
			_tables.resize(10);
		}
		~HashTable()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_tables[i] = nullptr;
			}
		}
		
		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
				return false;
			//扩容,这里负载因子可以最大到1
			if (_n == _tables.size())
			{
				size_t newSize = _tables.size() * 2;
				HashTable<K,V> newHT;
				newHT._tables.resize(newSize);
				//遍历旧表,插入新表
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						newHT.Insert(cur->_kv);
						cur = cur->_next;
					}
				}
				_tables.swap(newHT._tables);
			
			}
			//插入的新节点头插
			Hash hf;
			size_t hashi = hf(kv.first) % _tables.size();
			Node* newnode = new Node(kv);

			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			_n++;
			return true;
		}
		//哈希查找
		Node* Find(const K& key)
		{
			Hash hf;
			size_t hashi = hf(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			return nullptr;
		}
		//哈希删除
		bool Erase(const K& key)
		{
			Hash hf;
			size_t hashi = hf(key) % _tables.size();
			Node* cur = _tables[hashi];
			Node* prev = nullptr;
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					if (prev == nullptr)
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					return true ;
				}
				prev = cur;
				cur = cur->_next;
				
			}
			return false;
		}
		void Some()
		{
			size_t bucketSize = 0;
			size_t maxBucketLen = 0;
			size_t sum = 0;
			double averageBucketLen = 0;

			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				if (cur)
				{
					++bucketSize;
				}

				size_t bucketLen = 0;
				while (cur)
				{
					++bucketLen;
					cur = cur->_next;
				}

				sum += bucketLen;

				if (bucketLen > maxBucketLen)
				{
					maxBucketLen = bucketLen;
				}
			}

			averageBucketLen = (double)sum / (double)bucketSize;

			printf("all bucketSize:%d\n", _tables.size());
			printf("bucketSize:%d\n", bucketSize);
			printf("maxBucketLen:%d\n", maxBucketLen);
			printf("averageBucketLen:%lf\n\n", averageBucketLen);
		}
	private:
		vector<Node*> _tables;
		size_t _n = 0;
	};
	void TestHT1()
	{
		HashTable<int, int> ht;
		int a[] = { 4,14,24,34,5,7,1,15,25,3 };
		for (auto e : a)
		{
			ht.Insert(make_pair(e, e));
		}

		ht.Insert(make_pair(13, 13));

		cout << ht.Find(4) << endl;
		ht.Erase(4);
		cout << ht.Find(4) << endl;


	}
	void TestHT2()
	{
		string arr[] = { "香蕉", "甜瓜","苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
		HashTable<string, int> ht;
		for (auto& e : arr)
		{
			//auto ret = ht.Find(e);
			HashNode<string, int>* ret = ht.Find(e);
			if (ret)
			{
				ret->_kv.second++;
			}
			else
			{
				ht.Insert(make_pair(e, 1));
			}
		}
	}
	//测试效率
	void TestHT3()
	{
		const size_t N = 10000;

		unordered_set<int> us;
		set<int> s;
		HashTable<int, int> ht;

		vector<int> v;
		v.reserve(N);
		srand(time(0));
		for (size_t i = 0; i < N; ++i)
		{
			//v.push_back(rand()); // N比较大时,重复值比较多
			v.push_back(rand() + i); // 重复值相对少
			//v.push_back(i); // 没有重复,有序
		}

		// 21:15
		size_t begin1 = clock();
		for (auto e : v)
		{
			s.insert(e);
		}
		size_t end1 = clock();
		cout << "set insert:" << end1 - begin1 << endl;

		size_t begin2 = clock();
		for (auto e : v)
		{
			us.insert(e);
		}
		size_t end2 = clock();
		cout << "unordered_set insert:" << end2 - begin2 << endl;

		size_t begin10 = clock();
		for (auto e : v)
		{
			ht.Insert(make_pair(e, e));
		}
		size_t end10 = clock();
		cout << "HashTbale insert:" << end10 - begin10 << endl << endl;


		size_t begin3 = clock();
		for (auto e : v)
		{
			s.find(e);
		}
		size_t end3 = clock();
		cout << "set find:" << end3 - begin3 << endl;

		size_t begin4 = clock();
		for (auto e : v)
		{
			us.find(e);
		}
		size_t end4 = clock();
		cout << "unordered_set find:" << end4 - begin4 << endl;

		size_t begin11 = clock();
		for (auto e : v)
		{
			ht.Find(e);
		}
		size_t end11 = clock();
		cout << "HashTable find:" << end11 - begin11 << endl << endl;

		cout << "插入数据个数:" << us.size() << endl << endl;
		ht.Some();

		size_t begin5 = clock();
		for (auto e : v)
		{
			s.erase(e);
		}
		size_t end5 = clock();
		cout << "set erase:" << end5 - begin5 << endl;

		size_t begin6 = clock();
		for (auto e : v)
		{
			us.erase(e);
		}
		size_t end6 = clock();
		cout << "unordered_set erase:" << end6 - begin6 << endl;

		size_t begin12 = clock();
		for (auto e : v)
		{
			ht.Erase(e);
		}
		size_t end12 = clock();
		cout << "HashTable Erase:" << end12 - begin12 << endl << endl;
	}
	//结果HashTable尽显优势
}

如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长

在这里插入图片描述
在这里插入图片描述

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

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

相关文章

探索NLP中的核心架构:编码器与解码器的区别

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

【刷题专栏—突破思维】LeetCode 138. 随机链表的复制

前言 随机链表的复制涉及到复制一个链表&#xff0c;该链表不仅包含普通的next指针&#xff0c;还包含random指针&#xff0c;该指针指向链表中的任意节点或空节点。 文章目录 原地修改链表 题目链接&#xff1a; LeetCode 138. 随机链表的复制 原地修改链表 题目介绍&#xf…

Arduino驱动LM35线性温度传感器(温湿度传感器)

目录 1、传感器特性 2、控制器和传感器连线图 3、驱动程序 LM35半导体的温度传感器,可以用来对环境温度进行定性的检测。LM35半导体温度传感器是美国国家半导体公司生产的线性温度传感器。其测温范围是-40℃到150℃,灵敏度为10mV/℃,输出电压与温度成正比。

亚马逊云Amazon OpenSearch Serverless“利刃在手,‘向量’八方“

全Serverless架构新价值 随着Amazon OpenSearch Serverless正式上线“商用”&#xff0c;亚马逊云科技的全栈“Serverless”应用架构也“初见雏形”&#xff0c;这也意味着&#xff0c;未来企业可以在亚马逊云科技之上简单和轻松的搭建完整的无服务器应用架构。 数据也显示&am…

【LeetCode刷题日志】225.用队列实现栈

&#x1f388;个人主页&#xff1a;库库的里昂 &#x1f390;C/C领域新星创作者 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏✨收录专栏&#xff1a;LeetCode 刷题日志&#x1f91d;希望作者的文章能对你有所帮助&#xff0c;有不足的地方请在评论区留言指正&#xff0c;…

Git配置代理:fatal: unable to access*** github Failure when receiving data from

~吐槽一下 github自从被微软收购以后&#xff0c;大多数情况没点科技上网都进不去了&#xff0c;还是怀念以前随时访问的时光。 我一直都是开着系统代理的&#xff0c;但是今天拉一个项目发现拉不下来了&#xff0c;报错&#xff1a; fatal: unable to access https://githu…

人工智能基础_机器学习044_逻辑回归代码实现与手动计算概率---人工智能工作笔记0084

上面我们已经把逻辑回归的公式,以及,公式对应的图形都画画出来了,然后我们再来看看 如何用代码实现 可以看到上面是代码,咱们自己去写一下 import numpy as np from sklearn.linear_model import LogistieRegression from sklearn import datasets # 训练数据和测试数据拆分…

浅析AcrelEMS-CIA机场智慧能源管平台解决方案-安科瑞 蒋静

1 概述 机场智慧能源管平台解决方案对机场范围内变电站内的高低压配电设备 、 发电机、变压器 、UPS、EPS 、广场照明 、 室内照明 、通风及排水等机电设备进行实时分布式监控和集中管理 , 实现无人值守 , 确保高速公路安全畅通 , 提高 自动化管理水平 , 降低机电设备的运行维…

联想笔记本电脑触摸板失灵了怎么办

这里写自定义目录标题 thinkbook笔记本电脑触摸板失灵 thinkbook笔记本电脑触摸板失灵 由于重装系统&#xff0c;导致笔记本的触控板失灵&#xff0c; 网上说的办法有 1、按键盘上的ctrlf6键&#xff0c;打开触控板功能&#xff1a;无效 2、设置——>设备——>触控板&am…

buuctf-web-p6 [NPUCTF2020]web 狗

java: HelloWorld.class import java.io.PrintStream;public class HelloWorld {public static void main(String[] paramArrayOfString){System.out.println("众所周知&#xff0c;你是一名WEB选手&#xff0c;掌握javaweb也是一项必备技能&#xff0c;那么逆向个java应…

央企太卷.....来自央企的7个面试题,一个一个生产难题

说在前面 在40岁老架构师尼恩的&#xff08;50&#xff09;读者社群中&#xff0c;最近小伙伴&#xff0c;面试央企、美团、京东、阿里、 百度、头条等大厂。 下面是一个小伙伴成功拿到通过了一个央企设计研究院一面面试&#xff0c;现在把面试真题和参考答案收入咱们的宝典。…

Git企业开发级讲解(五)

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、bug 分⽀二、删除临时分支三、小结 一、bug 分⽀ 假如我们现在正在 dev2 分⽀上进⾏开发…

LeetCode994.腐烂的橘子

看完题我觉得这不是和上一道岛屿的题一样简单嘛&#xff0c;然后写了将近2个小时才写出来&#xff0c;我的思路就是&#xff0c;用check()先对grid检查一下&#xff0c;是否有以下情况&#xff1a; &#xff08;如果有1的周围都是空&#xff0c;则这个位置用不腐烂&#xff0c;…

清华学霸告诉你:如何自学人工智能?

清华大学作为中国顶尖的学府之一&#xff0c;培养了许多优秀的人才&#xff0c;其中不乏在人工智能领域有所成就的学霸。通过一位清华学霸的经验分享&#xff0c;揭示如何自学人工智能&#xff0c;帮助你在这场科技浪潮中勇往直前。 一、夯实基础知识 数学基础&#xff1a;学习…

【Qt开发流程】之HelloWorld程序

【Qt开发流程】之HelloWorld程序 目的编写程序新建项目文件说明及界面设计 程序运行及发布程序运行程序发布手动构建使用windeployqt进行构建 设置应用程序图标修改快捷键类型列表命令行编译程序命令行编译.ui文件自定义类项目模式及项目文件介绍项目模式项目文件 目的 从Hell…

vue --version无法显示,只弹出vs窗口

参考连接&#xff1a; nodejs环境配置&#xff08;解压包&#xff09;安装教程_nodejs解压版安装及环境配置_tubond的博客-CSDN博客 原因&#xff1a;环境没搞好&#xff0c;没有设置全局文件夹&#xff0c;node默认放在C盘了&#xff0c;C盘有权限。因为npm -i vue/cli创建…

2023最新最全【OpenMV】 入门教程

1. 什么是OpenMV OpenMV 是一个开源&#xff0c;低成本&#xff0c;功能强大的 机器视觉模块。 OpenMV上的机器视觉算法包括 寻找色块、人脸检测、眼球跟踪、边缘检测、标志跟踪 等。 以STM32F427CPU为核心&#xff0c;集成了OV7725摄像头芯片&#xff0c;在小巧的硬件模块上&a…

电磁场与电磁波part4--时变电磁场

1、采用洛伦兹条件使得矢量位 与标量位 分离在两个独立的方程中&#xff0c;且矢量位 仅与电流密度 有关&#xff0c;而标量位 仅与电荷密度 有关。 2、电磁能量守恒定理&#xff08;坡印廷定理&#xff09; 即减少的电磁能量电磁场所做的功流出的电磁能量 3、设u(r,t)是…

单元测试实战(四)MyBatis-Plus 的测试

为鼓励单元测试&#xff0c;特分门别类示例各种组件的测试代码并进行解说&#xff0c;供开发人员参考。 本文中的测试均基于JUnit5。 单元测试实战&#xff08;一&#xff09;Controller 的测试 单元测试实战&#xff08;二&#xff09;Service 的测试 单元测试实战&am…

Java学习 10.Java-类和对象

一、面向对象的初步认知 1.1 什么是面向对象 面向对象是解决问题的一种思想&#xff0c;主要依靠对象之间的交互完成一件事情&#xff0c;用面向对象的思想来设计程序&#xff0c;更符合人们对事物的认知&#xff0c;对于大型程序的设计、拓展以及维护都非常友好 1.2 面向对…