当前位置: 首页 > article >正文

18,C++——哈希

目录

一,unordered系列关联式容器

1,unordered_map

2,unordered_set

二、底层结构

1, 哈希概念

2,哈希函数

1)哈希函数设计原则

2)常见哈希函数

3, 哈希冲突

1)闭散列

2)开散列

三、哈希的实现

四、哈希的应用

1, 位图

1)经典题目

2)位图的概念

3)位图的应用

4)位图的实现

2, 布隆过滤器

1)布隆过滤器的概念

2)布隆过滤器的应用

3)布隆过滤器的实现

点个赞吧!666


一,unordered系列关联式容器

在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到$log_2N$,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。

最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同。

对比map/set的区别:

1,map和set遍历是有序的,unordered系列是无序的

2,map和set是双向迭代器,unordered系列是单向的

1,unordered_map

文档链接:https://cplusplus.com/reference/unordered_map/unordered_map/?kw=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. 它的迭代器至少是前向迭代器。

使用实例:

#pragma once

using namespace std;

#include "Hash.h"

namespace ccc
{
	template<class K, class Hash = HashFunc<K>>
	class unordered_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename Bucket::HashTable<K, K, Hash, SetKeyOfT>::iterator iterator;

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

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

		pair<iterator, bool> insert(const K& key)
		{
			return _ht.Insert(key);
		}
	private:
		Bucket::HashTable<K, K, Hash, SetKeyOfT> _ht;
	};

	void test_set()
	{
		unordered_set<int> s;
		s.insert(2);
		s.insert(3);
		s.insert(1);
		s.insert(2);
		s.insert(5);

		unordered_set<int>::iterator it = s.begin();
		//auto it = s.begin();
		while (it != s.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

}

2,unordered_set

文档链接:https://cplusplus.com/reference/unordered_set/unordered_set/?kw=unordered_set

文档翻译:

1,无序集是不按特定顺序存储唯一元素的容器,它允许根据各个元素的值快速检索各个元素。
2,在unordered_set中,元素的值同时是其,用于唯一标识它。键是不可变的,因此,unordered_set中的元素在容器中一次就不能修改 - 不过,它们可以入和删除。
3,在内部,unordered_set中的元素不按任何特定顺序排序,而是根据其哈希值组织到桶中,以允许直接通过其快速访问各个元素(平均平均时间复杂度保持不变)。
4,unordered_set 容器通过访问单个元素的速度比 set 容器更快,尽管它们通常对其元素子集进行范围迭代的效率较低。
5,容器中的迭代器至少是前向迭代器。

使用实例:

#pragma once

using namespace std;

#include "Hash.h"

namespace csy
{
	template<class K, class V, class Hash = HashFunc<K>>
	class unordered_map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename Bucket::HashTable<K, pair<K, V>, Hash, MapKeyOfT>::iterator iterator;

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

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

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

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
			return ret.first->second;
		}
	private:
		Bucket::HashTable<K, pair<K, V>, Hash, MapKeyOfT> _ht;
	};

	void test_map()
	{
		unordered_map<string, string> dict;
		dict.Insert(make_pair("sort", "排序"));
		dict.Insert(make_pair("string", "字符串"));
		dict.Insert(make_pair("left", "左边"));

		unordered_map<string, string>::iterator it = dict.begin();
		while (it != dict.end())
		{
			cout << it->first << ":" << it->second << endl;
			++it;
		}
		cout << endl;

		unordered_map<string, int> countMap;
		string arr[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11" };
		for (auto e : arr)
		{
			countMap[e]++;
		}

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

二、底层结构

1, 哈希概念

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

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

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

当向该结构中:

1)插入元素

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

2)搜索元素

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

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

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

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

2,哈希函数

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

1)哈希函数设计原则

哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间

哈希函数计算出来的地址能均匀分布在整个空间中

哈希函数应该比较简单

2)常见哈希函数

A,直接定址法--(常用)

取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B

优点:简单、均匀。

缺点:需要事先知道关键字的分布情况。

使用场景:适合查找比较小且连续的情况。

B,除留余数法--(常用)

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

3, 哈希冲突

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

解决哈希冲突的思路是:扩容负载因子。

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

1)闭散列

A,线性探测,往后占据下一个位置,容易大片冲突

B,二次探测,往后跳跃的占据位置,减少大片冲突,但还是无法避免

2)开散列

A,拉链法(哈希桶),用单链表指向下一个冲突的数据(冲突超过一定数量,就改成搜索树提高效率)

三、哈希的实现

#pragma once

// 仿函数,转换类型,就能取模找位置啦
template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};
// 仿函数——string特化版本
template<>
struct HashFunc<string>
{
	// BKDR算法
	size_t operator()(const string& key)
	{
		size_t val = 0;
		for (auto ch : key)
		{
			val *= 131;
			val += ch;
		}
		return val;
	}
};

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

	template<class K, class V>
	struct HashData
	{
		pair<K, V> _kv; // 键值key对应的数据value
		State _state = EMPTY; // 内存状态标识:空,存在,删除
	};

	// 设置第三个模板参数,方便各种类型键值的取模查找
	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
	public:
		bool Insert(const pair<K, V>& kv)
		{
			// 如果数据存在,插入错误
			if (Find(kv.first)) return false;

			// 扩容,载荷控制在70%以下
			if (_table.size() == 0 || 10 * _size / _table.size() >= 7)
			{
				// 计算新空间,创建新表,开辟空间
				size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;
				HashTable<K, V, Hash> newHT;
				newHT._table.resize(newSize);

				// 把旧表的数据映射到新表
				for (auto e : _table)
				{
					if (e._state == EXIST)
					{
						newHT.Insert(e._kv);
					}
				}

				// 把旧表转换成新表
				_table.swap(newHT._table);
			}

			// 线性探测查找位置, 存在数据的位置不能放值
			Hash hfc;
			size_t index = hfc(kv.first) % _table.size();
			while (_table[index]._state == EXIST)
			{
				index++;
				index = index % _table.size();
			}

			// 放数据,标识存在数据
			_table[index]._kv = kv;
			_table[index]._state = EXIST;
			_size++;

			// 插入成功
			return true;
		}

		HashData<K, V>* Find(const K& key)
		{
			if (_table.size() == 0) return nullptr;

			Hash hfc;
			size_t index = hfc(key) % _table.size();
			size_t sign = index;
			while (_table[index]._state != EMPTY)
			{
				if (_table[index]._state != DELETE
					&& _table[index]._kv.first == key)
				{
					return &_table[index];
				}

				index++;
				index = index % _table.size();

				if (index == sign) break;
			}

			return nullptr;
		}

		bool Erase(const K& key)
		{
			HashData<K, V>* ret = Find(key);
			if (ret)
			{
				ret->_state = DELETE;
				--_size;
				return true;
			}
			else
			{
				return false;
			}
		}

		void Print()
		{
			for (size_t i = 0; i < _table.size(); i++)
			{
				if (_table[i]._state == EXIST)
				{
					printf("[%d:%d] ", i, _table[i]._kv.second);
				}
				else
				{
					printf("[%d:*] ", i);
				}
			}
			cout << endl;
		}

	private:
		// vector<pair<K, V>> _table // 原始写法不带size
		vector<HashData<K, V>> _table; // 哈希表
		size_t _size = 0; // 表里存储了多少个数据
	};

	void test3()
	{
		HashTable<int, int> h1;

		int a[] = { 1, 11, 4, 15, 26, 7, 44, 9 ,10 };
		for (auto e : a)
		{
			h1.Insert(make_pair(e, e));
		}

		h1.Erase(4);
		cout << h1.Find(4) << endl;
		cout << h1.Find(44) << endl;

		h1.Print();
	}

	void test4()
	{
		HashTable<string, int> h2;

		string a[] = { "苹果", "草莓", "西瓜", "苹果", "草莓", "西瓜", "苹果", "草莓", "西瓜" };
		for (auto& e : a)
		{
			auto ptr = h2.Find(e);
			if (ptr)
			{
				ptr->_kv.second++;
			}
			else
			{
				h2.Insert(make_pair(e, 1));
			}
		}

		h2.Print();
	}

	void test5()
	{
		HashFunc<string> hash;

		// 如果算出来的值一样,位置会冲突
		cout << hash("abcd") << endl;
		cout << hash("cdab") << endl;
		cout << hash("cadb") << endl;
		cout << hash("dcba") << endl;

	}
}


namespace HashBucket
{
	template<class K, class V>
	struct HashNode
	{
		pair<K, V> _kv; // 键值key对应的数据value
		HashNode<K, V>* _next; // 链接下一个节点

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

	template<class K,class V,class Hash = HashFunc<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;
	public:
		~HashTable()
		{
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					Node* next = cur->_next;
					free(cur);
					cur = next;
				}
				_tables[i] = nullptr;
			}
		}

		// 开辟空间大小为质数,能减少哈希冲突
		size_t Hash_tables_new_capacity(size_t n)
		{
			static const size_t arr_num = 28;
			static const size_t arr[arr_num] =
			{
				53,			 97,		  193,	      389,	      769,
				1543,		 3079,		  6151,	      12289,	  24593,
				49157,		 98317,		  196613,	  393241,	  786433,
				1572869,	 3145739,	  6291469,    12582917,   25165843,
				50331653,	 100663319,	  201326611,  402653189,  805306457,
				1610612741,  3221225473,  4294967291
			};

			for (size_t i = 0; i < arr_num; ++i)
			{
				if (arr[i] > n)
				{
					return arr[i];
				}
			}

			return -1;
		}

		bool Insert(const pair<K, V>& kv)
		{
			// 去重
			if (Find(kv.first)) return false;
			//扩容,负载因子到100%
			if (_size == _tables.size())
			{
				// 计算新空间,创建新表,开辟空间
				//size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				size_t newSize = Hash_tables_new_capacity(_tables.size());
				vector<Node*> newTable;
				newTable.resize(newSize, nullptr);

				// 旧表中的节点,移动到新表
				for (size_t i = 0; i < _tables.size(); ++i)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;
						 
						Hash hfc;
						size_t hashi = hfc(cur->_kv.first) % newSize;
						cur->_next = newTable[hashi];
						newTable[hashi] = cur;

						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newTable);
			}
			// 找位置
			Hash hfc;
			size_t hashi = hfc(kv.first) % _tables.size();
			// 头插
			Node* newnode = new Node(kv);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_size;
			// 返回
			return true;
		}

		Node* Find(const K& key)
		{
			if (_tables.size() == 0) return nullptr;
			Hash hfc;
			size_t hashi = hfc(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)
		{
			if (_tables.size() == 0) return false;
			Hash hfc;
			size_t hashi = hfc(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 = cur->_next;
					}
					delete cur;
					return true;
				}
				else // 没找到
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			return false;
		}
		
		// 数据个数
		size_t Size()
		{
			return _size;
		}

		// 表的长度
		size_t TablesSize()
		{
			return _tables.size();
		}

		// 桶的个数
		size_t BucketNum()
		{
			size_t num = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				if (_tables[i]) 
				{ 
					++num;
				};
			}
			return num;
		}

		// 桶的长度
		size_t BucketSize()
		{
			size_t maxLen = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				size_t len = 0;
				Node* cur = _tables[i];
				while (cur)
				{
					++len;
					cur = cur->_next;
				}
				if (len > maxLen)
				{
					maxLen = len;
				}
			}
			return maxLen;
		}

	private:
		vector<Node*> _tables;
		size_t _size = 0;	
	};

	void test6()
	{
		HashTable<int, int> h1;

		int a[] = { 1, 11, 4, 15, 26, 7, 44, 55, 99, 78 };
		for (auto e : a)
		{
			h1.Insert(make_pair(e, e));
		}

		h1.Insert(make_pair(22, 22));
		h1.Erase(1);
		h1.Erase(11);
		h1.Erase(4);
		h1.Erase(44);
	}

	void test7()
	{
		HashTable<string, int> h2;

		string a[] = { "苹果", "草莓", "西瓜", "苹果", "草莓", "西瓜", "苹果", "草莓", "西瓜" };
		for (auto& e : a)
		{
			auto ptr = h2.Find(e);
			if (ptr)
			{
				ptr->_kv.second++;
			}
			else
			{
				h2.Insert(make_pair(e, 1));
			}
		}

		h2.Insert(make_pair("葡萄", 7));
	}

	void test8()
	{
		int n = 1000000;
		vector<int> v;
		v.reserve(n);
		srand(time(0));
		for (int i = 0; i < n; ++i)
		{
			//v.push_back(i);
			v.push_back(rand() + i);  // 重复少
			//v.push_back(rand());  // 重复多
		}

		size_t begin1 = clock();
		HashTable<int, int> ht;
		for (auto e : v)
		{
			ht.Insert(make_pair(e, e));
		}
		size_t end1 = clock();

		cout << "数据个数:" << ht.Size() << endl;
		cout << "表的长度:" << ht.TablesSize() << endl;
		cout << "桶的个数:" << ht.BucketNum() << endl;
		cout << "平均每个桶的长度:" << (double)ht.Size() / (double)ht.BucketNum() << endl;
		cout << "最长的桶的长度:" << ht.BucketSize() << endl;
		cout << "负载因子:" << (double)ht.Size() / (double)ht.TablesSize() << endl;
	}
};

namespace Bucket
{
	template<class T>
	struct HashNode
	{
		T _data; // T类型数据data
		HashNode<T>* _next; // 链接下一个节点

		HashNode(const T& data)
			:_data(data)
			, _next(nullptr)
		{}
	};

	// 前置声明
	template<class K, class T, class Hash, class KeyOfT>
	class HashTable;

	// 迭代器,只有单向
	template<class K, class T, class Hash, class KeyOfT>
	struct __HashIterator
	{
		typedef HashNode<T> Node;
		typedef HashTable<K, T, Hash, KeyOfT> HT;
		typedef __HashIterator<K, T, Hash, KeyOfT> Self;

		Node* _pnode;
		HT* _pht;

		__HashIterator(Node* pnode, HT* pht)
			:_pnode(pnode)
			, _pht(pht)
		{}

		T& operator * ()
		{
			return _pnode->_data;
		}

		T* operator -> ()
		{
			return &_pnode->_data;
		}
		
		bool operator == (const Self& s) const
		{
			return _pnode == s._pnode;
		}

		bool operator != (const Self& s) const
		{
			return _pnode != s._pnode;
		}

		Self& operator ++ ()
		{
			if (_pnode->_next) // 当前桶中迭代
			{
				_pnode = _pnode->_next;
			}
			else // 走到下一个桶
			{
				Hash hfc;
				KeyOfT kot;
				size_t i = hfc(kot(_pnode->_data)) % _pht->_tables.size();
				++i;

				for (; i < _pht->_tables.size(); ++i)
				{
					if (_pht->_tables[i])
					{
						_pnode = _pht->_tables[i];
						break;
					}
				}

				if (i == _pht->_tables.size())
				{
					_pnode = nullptr;
				}
			}
			return *this;
		}

	};

	template<class K, class T, class Hash, class KeyOfT>
	class HashTable
	{
		typedef HashNode<T> Node;

		template<class K, class T, class Hash, class KeyOfT>
		friend struct __HashIterator;
	public:
		typedef __HashIterator<K, T, Hash, KeyOfT> iterator;

		iterator begin()
		{
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				if (_tables[i])
				{
					return iterator(_tables[i], this);
				}
			}
			return end();
		}

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

		~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;
			}
		}

		// 开辟空间大小为质数,能减少哈希冲突
		inline size_t Hash_tables_new_capacity(size_t n)
		{
			static const size_t arr_num = 28;
			static const size_t arr[arr_num] =
			{
				53,			 97,		  193,	      389,	      769,
				1543,		 3079,		  6151,	      12289,	  24593,
				49157,		 98317,		  196613,	  393241,	  786433,
				1572869,	 3145739,	  6291469,    12582917,   25165843,
				50331653,	 100663319,	  201326611,  402653189,  805306457,
				1610612741,  3221225473,  4294967291
			};

			for (size_t i = 0; i < arr_num; ++i)
			{
				if (arr[i] > n)
				{
					return arr[i];
				}
			}

			return -1;
		}

		pair<iterator, bool> Insert(const T& data)
		{
			Hash hfc;
			KeyOfT kot;

			// 去重
			//if (Find(kv.first)) return false;
			iterator ret = Find(kot(data));
			if (ret != end())
			{
				return make_pair(ret, false);
			}

			//扩容,负载因子到100%
			if (_size == _tables.size())
			{
				// 计算新空间,创建新表,开辟空间
				//size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				size_t newSize = Hash_tables_new_capacity(_tables.size());
				vector<Node*> newTable;
				newTable.resize(newSize, nullptr);

				// 旧表中的节点,移动到新表
				for (size_t i = 0; i < _tables.size(); ++i)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;

						size_t hashi = hfc(kot(cur->_data)) % newSize;
						cur->_next = newTable[hashi];
						newTable[hashi] = cur;

						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newTable);
			}
			// 找位置
			size_t hashi = hfc(kot(data)) % _tables.size();
			// 头插
			Node* newnode = new Node(data);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_size;
			// 返回
			return make_pair(iterator(newnode, this), true);
		}

		iterator Find(const K& key)
		{
			if (_tables.size() == 0) return end();
			Hash hfc;
			KeyOfT kot;
			size_t hashi = hfc(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return iterator(cur, this);
				}
				cur = cur->_next;
			}
			return end();
		}

		bool Erase(const K& key)
		{
			if (_tables.size() == 0) return false;
			Hash hfc;
			KeyOfT kot;
			size_t hashi = hfc(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_data == key) // 找到
				{
					if (prev == nullptr) // 头删
					{
						_tables[hashi] = cur->_next;
					}
					else // 中间删
					{
						prev = cur->_next;
					}
					delete cur;
					return true;
				}
				else // 没找到
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			return false;
		}

		// 数据个数
		size_t Size()
		{
			return _size;
		}

		// 表的长度
		size_t TablesSize()
		{
			return _tables.size();
		}

		// 桶的个数
		size_t BucketNum()
		{
			size_t num = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				if (_tables[i])
				{
					++num;
				};
			}
			return num;
		}

		// 桶的长度
		size_t BucketSize()
		{
			size_t maxLen = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				size_t len = 0;
				Node* cur = _tables[i];
				while (cur)
				{
					++len;
					cur = cur->_next;
				}
				if (len > maxLen)
				{
					maxLen = len;
				}
			}
			return maxLen;
		}

	private:
		vector<Node*> _tables;
		size_t _size = 0;
	};
}

四、哈希的应用

1, 位图

1)经典题目

1,给40亿个不重复的无符号整数和一个无符号整数,没排过序,如何快速判断这一个数是否在这40亿个数中?

2. 给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?

3. 位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数

解决方法:

A. 遍历,时间复杂度O(N)

B. 排序(O(NlogN)),利用二分查找: logN

C. 位图解决

2)位图的概念

所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。

通常是用来判断某个数据存不存在的。

位图的特点是:快、节省空间,但相对局限,只能映射处理整形。

数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一 个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0 代表不存在。比如下图:

3)位图的应用

1. 快速查找某个数据是否在一个集合中

2. 排序 + 去重

3. 求两个集合的交集、并集等

4. 操作系统中磁盘块标记

4)位图的实现
template<size_t N>
class BitMap
{
public:
	BitMap()
	{
		_bits.resize(N / 8 + 1, 0);
	}

	void push(size_t x)
	{
		size_t i = x / 8;
		size_t j = x % 8;

		_bits[i] |= (1 << j);// 或等
	}

	void pop(size_t x)
	{
		size_t i = x / 8;
		size_t j = x % 8;

		_bits[i] &= ~(1 << j);// 与等
	}

	bool empty(size_t x)
	{
		size_t i = x / 8;
		size_t j = x % 8;

		return _bits[i] & (1 << j);// 与
	}
private:
	vector<char> _bits;
};

void test_bitmap1()
{
	BitMap<100> bm1;

	bm1.push(8);
	bm1.push(9);
	bm1.push(20);
	
	cout << bm1.empty(8) << endl;
	cout << bm1.empty(9) << endl;
	cout << bm1.empty(20) << endl;

	bm1.pop(8);
	bm1.pop(9);
	bm1.pop(20);

	cout << bm1.empty(8) << endl;
	cout << bm1.empty(9) << endl;
	cout << bm1.empty(20) << endl;

	BitMap<-1> bm2;
	BitMap<1024*1024*1024*4> bm3;
	BitMap<0xffffffff> bm4;
}

// 小题目
// 1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数
// kv模型统计次数
// 0次以上		00
// 1次以上		01
// 2次以上		10
template<size_t N>
class TowBitMap
{
public:
	void push(size_t x)
	{
		bool insert1 = _tbm1.empty(x);
		bool insert2 = _tbm2.empty(x);
	
		// 00
		if (insert1 == false && insert2 == false)
		{
			// -> 01
			_tbm2.push(x);
		}
		else if (insert1 == false && insert2 == true)
		{
			// -> 10
			_tbm1.push(x);
			_tbm2.pop(x);
		}
		else if (insert1 == true && insert2 == false)
		{
			// -> 01
			_tbm1.push(x);
			_tbm2.push(x);
		}
	}
	
	// 记录出现只一次的数
	void print_once_num()
	{
		for (size_t i = 0; i < N; ++i)
		{
			if (_tbm1.empty(i) == false && _tbm2.empty(i) == true)
			{
				cout << i << endl;
			}
		}
	}

private:
	BitMap<N> _tbm1;
	BitMap<N> _tbm2;
};

void test_bitmap2()
{
	int a[] = { 3, 4, 5, 2, 3, 4, 4, 4, 4, 12, 77, 65, 44, 4, 44, 99, 33, 33, 33, 6, 5, 34, 12 };

	TowBitMap<100> tbm1;
	for (auto e : a)
	{
		tbm1.push(e);
	}

	tbm1.print_once_num();
}

2, 布隆过滤器

1)布隆过滤器的概念

布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的,一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询。

布隆过滤器其实就是位图的一个变形和延申,虽然无法避免存在哈希冲突,但我们可以想办法降低误判的概率。

当一个数据映射到位图中时,布隆过滤器会用多个哈希函数将其映射到多个比特位,当判断一个数据是否在位图当中时,需要分别根据这些哈希函数计算出对应的比特位,如果这些比特位都被设置为1则判定为该数据存在,否则则判定为该数据不存在。

布隆过滤器使用多个哈希函数进行映射,目的就在于降低哈希冲突的概率,一个哈希函数产生冲突的概率可能比较大,但多个哈希函数同时产生冲突的概率可就没那么大了。

2)布隆过滤器的应用

通常用于快速查询数据是否存在,并且可以存在误判。

3)布隆过滤器的实现
struct HashBKDR
{
	size_t operator()(const string& key)
	{
		size_t val = 0;
		for (auto ch : key)
		{
			val *= 131;
			val += ch;
		}

		return val;
	}
};

struct HashAP
{
	size_t operator()(const string& key)
	{
		size_t hash = 0;
		for (size_t i = 0; i < key.size(); i++)
		{
			if ((i & 1) == 0)
			{
				hash ^= ((hash << 7) ^ key[i] ^ (hash >> 3));
			}
			else
			{
				hash ^= (~((hash << 11) ^ key[i] ^ (hash >> 5)));
			}
		}
		return hash;
	}
};

struct HashDJB
{
	size_t operator()(const string& key)
	{
		size_t hash = 5381;
		for (auto ch : key)
		{
			hash += (hash << 5) + ch;
		}

		return hash;
	}
};

// N表示准备映射N个数据
// K表示数据类型
// Hash123表示映射的3个位置
template<size_t N,
	class K = string,
	class Hash1 = HashBKDR, 
	class Hash2 = HashAP, 
	class Hash3 = HashDJB>
class BloomFilter
{
public:
	void Set(const K& key)
	{
		size_t hash1 = Hash1()(key) % (_ratio * N);
		_bits->push(hash1);

		size_t hash2 = Hash2()(key) % (_ratio * N);
		_bits->push(hash2);

		size_t hash3 = Hash3()(key) % (_ratio * N);
		_bits->push(hash3);
	}

	bool Test(const K& key)
	{
		size_t hash1 = Hash1()(key) % (_ratio * N);
		if (!_bits->empty(hash1)) return false; // 准确的

		size_t hash2 = Hash2()(key) % (_ratio * N);
		if (!_bits->empty(hash2)) return false; // 准确的

		size_t hash3 = Hash3()(key) % (_ratio * N);
		if (!_bits->empty(hash3)) return false; // 准确的

		return true; // 可能存在误判
	}
private:
	const static size_t _ratio = 5; // 1个值映射3个位置,开5倍的空间就比较合适
	BitMap<N*_ratio>* _bits = new BitMap<N* _ratio>; // 默认初始化
};

void Test_BloomFilter_1()
{
	BloomFilter<10> bf;

	string arr1[] = { "苹果", "西瓜", "阿里", "美团", "苹果", "字节", "西瓜", "苹果", "香蕉", "苹果", "腾讯" };
	for (auto& str : arr1)
	{
		bf.Set(str);
	}
	for (auto& str : arr1)
	{
		cout << bf.Test(str) << endl;
	}
	cout << endl << endl;

	string arr2[] = { "苹果111", "西瓜", "阿里2222", "美团", "苹果dadcaddxadx", "字节", "西瓜sSSSX", "苹果 ", "香蕉", "苹果$", "腾讯" };
	for (auto& str : arr2)
	{
		cout << str << ":" << bf.Test(str) << endl;
	}
	cout << endl << endl;
}

点个赞吧!666


http://www.kler.cn/a/599472.html

相关文章:

  • 第3章 Internet主机与网络枚举(网络安全评估)
  • Log4j2 的核心实现和源码分析
  • day 6 中断
  • 求二叉搜索树中的众数的三种方法
  • [Android] NFC卡模拟 9.05 模拟NFC门禁卡 电梯卡等 手机代替卡片
  • <项目> 高并发服务器的HTTP协议支持
  • QML指示控件:ScrollBar与ScrollIndicator
  • 复杂任务需要多agent协同处理,对其进行逻辑编排和参数调用
  • 鸿蒙特效教程10-卡片展开/收起效果
  • 数据结构篇:空间复杂度和时间复杂度
  • netplan是如何操控NetworkManager的? 笔记250324
  • 车载以太网网络测试 -23【TCPUDP通信示例】
  • 蓝桥杯——嵌入式学习日记
  • 借助AI Agent实现数据分析
  • Python虚拟环境:从入门到实战指南
  • 在 ASP.NET Core 中实现限流(Rate Limiting):保护服务免受滥用与攻击
  • ABB机器人更换机器人本体编码器电池的具体步骤
  • 蓝桥杯,冬奥大抽奖
  • 中间件解析漏洞之Tomcat集合
  • 操作系统必知的面试题