unordered_map和unordered_set的模拟实现

Posted 小倪同学 -_-

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了unordered_map和unordered_set的模拟实现相关的知识,希望对你有一定的参考价值。

文章目录

哈希表模拟代码

unordered_map和unordered_set底层是用哈希桶实现的,下面是模拟实现哈希表和哈希桶的代码。

闭散列

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

#pragma once
#include<vector>

namespace close_hash

	enum Status
	
		EMPTY,
		EXITS,
		DELETE
	;

	template<class K, class V>
	struct HashData
	
		pair<K, V> _kv;
		Status _status = EMPTY;
	;

	template<class K>
	struct HashFanc
	
		size_t operator()(const K& key)
		
			return key;
		
	;

	// 特化
	template<>
	struct HashFanc<string>
	
		size_t operator()(const string& key)
		
			size_t hash = 0;
			for (size_t i = 0; i < key.size(); i++)
			
				hash *= 131;
				hash += key[i];
			
			return hash;
		
	;

	struct HashFuncString
	
		size_t operator()(const string& key)
		
			// BKDR Hash思想
			size_t hash = 0;
			for (size_t i = 0; i < key.size(); ++i)
			
				hash *= 131;
				hash += key[i];
			

			return hash;
		
	;

	template<class K,class V,class Hash=HashFanc<K>>
	class HashTable
	
	public:
		bool Erase(const K& key)
		
			HashData<K, V>* ret = Find(key);
			if (ret == nullptr)
			
				return false;
			
			else
			
				ret->_status = DELETE;
				_n--;
				return true;
			
		

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

			Hash hf;
			size_t start = hf(key)%_tables.size();
			size_t i = 0;
			size_t index = start + i;
			while (_tables[index]._status != EMPTY)
			
				if (_tables[index]._kv.first == key
					&& _tables[index]._status == EXITS)
				
					return &_tables[index];
				
				else
				
					++i;
					index = start + i; // 线性探测
					//index = start + i*i; // 二次探测
					index %= _tables.size();
				
			

			return nullptr;
		

		bool Insert(const pair<K, V>& kv)
		
			if (Find(kv.first))
			
				return false;
			

			if (_tables.size() == 0 || _n*10 / _tables.size() >= 7)
			
				size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				HashTable<K, V, Hash> newHT;
				newHT._tables.resize(newSize);
				for (auto& e : _tables)
				
					if (e._status == EXITS)
					
						newHT.Insert(e._kv);
					
				
				
				_tables.swap(newHT._tables);
			

			Hash hf;
			size_t start = hf(kv.first)%_tables.size();
			size_t i = 0;
			size_t index = start + i;
			// 线性探测
			while (_tables[index]._status == EXITS)
			
				++i;
				index = start + i; // 线性探测
				//index = start + i*i; // 二次探测
				index %= _tables.size();
			

			_tables[index]._kv = kv;
			_tables[index]._status = EXITS;
			++_n;

			return true;
		
	private:
		vector<HashData<K, V>> _tables;
		size_t _n = 0; // 存储有效数据的个数
	

开散列

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

namespace bucket_bush

	template<class K>
	struct HashFanc
	
		size_t operator()(const K& key)
		
			return key;
		
	;

	size_t GetNextPrime(size_t prime)
	
		const int PRIMECOUNT = 28;
		//素数序列
		static const size_t primeList[PRIMECOUNT] =
		
			53ul, 97ul, 193ul, 389ul, 769ul,
			1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
			49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
			1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
			50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
			1610612741ul, 3221225473ul, 4294967291ul
		;

		size_t i = 0;
		for (; i < PRIMECOUNT; ++i)
		
			if (primeList[i] > prime)
				return primeList[i];
		

		return primeList[i];
	

	// 特化
	template<>
	struct HashFanc<string>
	
		size_t operator()(const string& key)
		
			size_t hash = 0;
			for (size_t i = 0; i < key.size(); i++)
			
				hash *= 131;
				hash += key[i];
			
			return hash;
		
	;

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

		// 构造结点
		HashNode(const pair<K, V>& kv)
			:_kv(kv)
			, _next(nullptr)
		
	;

	template<class K,class V,class Hash=HashFanc<K>>
	class HashTable
	
		typedef HashNode<K, V> Node;
	public:
			// 拷贝构造
		HashTable(const HashTable& ht)
		
			_tables.resize(ht._table.size());// 调整表的大小
			for (int i = 0; i < ht._tables.size(); i++)// 将结点拷贝到新表中
			
				if (ht._tables[i])
				
					Node* cur = ht._tables[i];
					while (cur)
					
						Node* copy = new Node(cur->_data);
						copy->_next = _tables[i];
						_tables[i] = copy;
						cur = cur->_next;
					
				
			
			_n = ht._n;// 调整数据
		

		// 赋值运算符重载
		HashTable& operator=(HashTable ht)
		
			//交换哈希表中两个成员变量的数据
			_table.swap(ht._table);
			swap(_n, ht._n);

			return *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;
			
		

		bool Erase(const K& key)
		
			if (_tables.size() == 0)
			
				return false;
			

			Hash hf;
			int index = hf(key)%_tables.size();			
			Node* prev = nullptr;
			Node* cur = _tables[index];
			while (cur)
			
				if (cur->_kv.first == key)
				
					if (prev == nullptr)// cur是头结点
					
						_tables[index] = cur->_next;
					
					else
					
						prev->_next = cur->_next;
					

					delete cur;
					--_n;
					return true;
				
				else
				
					prev = cur;
					cur = cur->_next;
				
			
			return false;
		

		Node* Find(const K& key)
		
			if (_tables.size() == 0)
			
				return nullptr;
			

			Hash hf
			size_t index = hf(key)%_tables.size();
			Node* cur = _tables[index];
			while (cur)
			
				if (cur->_kv.first == key)
				
					return cur;
				
				else
				
					cur = cur->_next;
				
			

			return nullptr;
		

		bool Insert(const pair<K, V>& kv)
		
			Hash hf;
			// 负载因子为1时扩容
			if (_n == _tables.size())
			
				//size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				size_t newSize = GetNextPrime(_tables.size());

				vector<Node*> newtables;
				newtables.resize(newSize, nullptr);
				for (size_t i = 0; i < _tables.size(); i++)
				
					Node* cur = _tables[i];
					while (cur)
					
						Node* next = cur->_next;

						size_t index = hf(cur->_kv.first)%newSize;
						cur->_next = newtables[index];
						newtables[index] = cur;

						cur = next;
					
					_tables[i] = nullptr;
				
				newtables.swap(_tables);
			

			size_t index = hf(kv.first)%_tables.size();
			Node* cur = _tables[index];
			while (cur)
			
				if (cur->_kv.first == kv.first)
					return false;
				else
					cur = cur->_next;
			

			Node* newnode = new Node(kv);
			newnode->_next = _tables[index];
			_tables[index] = newnode;

			++_n;
			return true;
		
	private:
		vector<Node*> _tables;
		size_t _n=0;
	;

哈希表模板参数

unordered_set是K模型的容器,unordered_map是KV模型的容器,为了用一份哈希表同时封装出K模型和KV模型的容器,我们需要使用模板。

原来哈希桶结点

template<class K,class V>
struct HashNode

	pair<K, V> _kv;
	HashNode* _next;

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

将原先键值对模板<class K,class V>修改为<class T>,这里的T可以表示K模型也可表示KV模型,修改后如下

template<class T>
struct HashNode

	T _data;
	HashNode<T>* _next;

	// 构造函数
	HashNode(const T& data)
		:_data(data)
		, _next(nullptr)
	
;

模板参数中的仿函数

由于结点当中存储的是T,这个T可能是Key,也可能是<Key, Value>键值对。那么当我们需要进行结点的键值比较时该怎么办呢?

这里就需要用到仿函数了,利用仿函数将T中的K值取出来进行比较。

仿函数,就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了。

template<class K, class V, class Hash = HashFanc<K>>
class unordered_map

	struct MapKeyOfT
	
		const K& operator()(const pair <const K, V>& kv) const//返回键值对当中的键值Key
		
			return kv.first;
		
	;
private:
	bucket_hash::HashTable<K, pair<const K, V>, Hash, MapKeyOfT> _ht;
;

对于哈希表来说,它并不知道上层容器是map还是set,因此当需要进行两个结点键值的比较时,底层哈希表都会通过传入的仿函数来获取键值Key。

所以,set容器也需要仿函数

template<class K, class Hash = HashFanc<K>>
class unordered_set

	struct SetKeyOfT
	
		const K& operator()(const K& key) const//返回键值Key
		
			return key;
		
	;
private:
	bucket_hash::HashTable<K, K, Hash, SetKeyOfT> _ht;
;		

字符串哈希算法

而字符串并不是整型,也就意味着字符串不能直接用于计算哈希地址,我们需要通过某种方法将字符串转换成整型后,才能代入哈希函数计算哈希地址。

但遗憾的是,我们无法找到一种能实现字符串和整型之间一对一转换的方法,因为在计算机中,整型的大小是有限的,比如用无符号整型能存储的最大数字是4294967295,而众多字符能构成的字符串的种类却是无限的。

这里我们需要利用字符串哈希算法使每个字符串对应一个整数,虽然无法实现一 一对应,但是能大大减少重复的概率。

template<class K>
struct HashFanc

	size_t operator()(const K& key)
	
		return key;
	
;
// string类型的特化
template<>
struct HashFanc<string>

	size_t operator()(const string& key)// BKDRHash算法
	
		size_t hash = 以上是关于unordered_map和unordered_set的模拟实现的主要内容,如果未能解决你的问题,请参考以下文章

C++之unordered_map和unordered_set以及哈希详解

map和unordered_map

C++ unordered_map 的 rehash() 和 reserve() 方法有啥区别?

unordered_map和map的区别

unordered_map: find() 和 count() 哪个更快?

unordered_map 哈希函数 / 如何防止 unordered_map 被卡