unordered_set 和 unordered_map的模拟实现(c++)

前言:

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

1底层结构

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

Unordered_map

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

Unordered_set

**1)**unoredered_set不再以键值对的形式存储数据,而是直接存储数据的值。

**2)**容器内部存储的各个元素的值都互不相等,且不能被修改。

**3)**不会对内部存储的数据进行排序(与unordered_map一样,归结于其底层结构)

1.1 哈希概念

✍顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经 过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O(logN)搜索的效率取决于搜索过程中元素的比较次数. 理想的搜索方法可以不经过任何比较一次直接从表中得到要搜索的元素。 如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

当向该结构中:

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

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

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

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

用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快 。按照上述哈希方式,向集合中插入元素44,该怎么插入呢?插入到哪一个位置呢?此时就引出了一个新的问题:哈希冲突。

1.2哈希冲突

🎤:不同的关键字经过相同的哈希函数的计算后得出相同的哈希地址,这种现象称为哈希冲突。

1.3哈希函数

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

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

常见哈希函数

  1. 直接定址法:

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

  1. 除留余数法:

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

1.4哈希冲突解决

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

1.4.1 闭散列

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

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

  • 对于插入的情况来说:首先通过哈希函数获取到该元素在哈希表中的位置,如果当前的位置上没有元素,则直接插入,如果已经有了插入的元素则发生哈希冲突,从当前位置向后探测直到直到下一个空位置,插入,如上面的例子,我们需要插入44时,按哈希函数的计算,44的地址应该在哈希表的4处,但是该地址已经有了元素14,所以向后寻找下一个空的位置,找到了下标为6的地址,将44存储到该地址。
  • 对于线性探测的删除来说,不能随便删除掉表中的元素,如果直接删除掉已有的元素,则会直接影响到其他元素的搜索。比如删除掉元素14,44的查找可能会受到影响(当删除14的时候,在搜索44的时候,会从哈希地址为4的位置查找,而14删除若为空,则不会继续搜索下去)。那么该如何解决这个问题呢?我们采用标记的伪删除法来删除一个元素。即对哈希表中的每一个空间都给个标记,分别用三种标记代表当前位置的三种状态:位置空、已经有元素和元素已经删除。

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

线性探测的缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据的堆积,即本该不属于该位置的关键码因为哈希冲突占据了该位置,使得本该属于该位置的关键码需要找寻其他空位置,随着在表中插入数据的不断增大,会产生"堆积",使得寻找某关键码的位置时需要进行多次比较,导致搜索的效率降低。

☢二次探测 :上述的线性探测的缺陷是产生冲突的数据容易堆积在一起,为了优化这个问题,提出了另一种二次探测的方法,当当前位置发生哈希冲突时,改变寻找下一个空位置的策略: 下一个位置为当前冲突位置后偏移量为(1,2,3...)的二次方地址处。

☻负载因子:

散列表的负载因子定义为 a=填入表中的元素/散列表的长度。

a是散列表装满程度的标志因子,由于表长是定值,因此当插入表中的元素越多时a就越大,产生哈希冲突的可能性就越大,反之,产生冲突的可能性就越小。

对于开放定址法,负载因子是一个很重要的因素,应该严格控制在0.7-.8以下

1.4.2 开散列

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

总结:

应用链地址法处理溢出的情况时需要增设链接指针,看起来似乎增加了存储开销,但是事实上由于开地址法必须保持大量的空闲空间以确保搜索的效率,而表项所占的空间又比指针大很多,所以使用链地址法比开放空间法更节省存储空间。

2哈希表(闭散列)的实现

2.1成员变量

哈希表(闭散列)的实现实际上就是开一个数组,为了简便哈希表底层我们直接用库里的vector,再定义一个变量_num 用来标记哈希表中存放的有效元素的个数

cpp 复制代码
​
	class HashTable
	{
	public:
	//1.增
    //2.删
    //3.查
	private:
		std::vector<HashData> _tables;
		size_t _num = 0;//哈希表中存放的有效元素的个数

	};

​

在前面的闭散列提到为了方便哈希表的删除我们需要对每个地址进行标记,当前位置是否是空状态,是否为存在元素状态,是否为删除过元素的状态。对于哈希表中的每一个元素的节点,其中不仅要存当前元素的值还需要存储该节点的状态。

cpp 复制代码
enum State
	{
		EMPTY,//空
		EXITS,//存在
		DELETE,//删除
	};
	template<class T>
	struct HashData
	{
		T _data;
		State _state;
	};

2.2插入数据insert()

如何插入数据:

  • 首先先通过哈希函数计算出该元素在哈希表中的存储位置
  • 接着判断当前节点的状态,如果为空或者删除状态则直接插入,否则判断当前结点的元素是否与插入的元素相同(若相同则不插入),不相同则向后寻找下一个空或删除状态的节点再插入。

如果我们在哈希表中存储的是整型,那么通过哈希函数计算出它的存储位置是可以的,但是倘若我们要存储的是pair键值对呢?所以这里我们还需要仿函数来配合,通过重载operator()来实现对不同类型的数据的返回以方便计算其存储位置,且对插入的数据进行比较大小。关于仿函数上一篇封装红黑树实现set 和map-CSDN博客有所说明。

cpp 复制代码
template<class K,class T,class KeyOfT>
	class HashTable
	{
		typedef HashData<T> HashData;
	public:
		KeyOfT koft;//仿函数用来取出T中k,若是pair则koft(make_pair(1,2))=1
		bool Insert(const T& d)
		{
			//1.增容//
            //若表的数据为0.我们开出10个空间,或者当存储数/表的大小为0.7也增容,为了解决哈希冲突
			if (_tables.size() == 0 || _num * 10 / _tables.size() >= 7)
			{
                //创建一个新表
				std::vector<HashData> newtables;
               //新表容量为旧表的2倍
				size_t newsize=_tables.size() == 0 ? 10 : _tables.size() * 2;
                //新表开出newsize大小空间并进行初始化0
				newtables.resize(newsize);
                //遍历旧表,把旧表的数据存储到新表,因为新表容量变为2倍,所以在新表存储位置改变
				for (size_t i = 0; i < _tables.size(); ++i)
				{
                    //若旧表_tables[i]_state状态为存在,则说明有数据
					if (_tables[i]._state == EXITS)
					{
                        //计算旧表数据在新表的存储位置
						size_t index = koft(_tables[i]._data) % newtables.size();
                        //当新表中此时位置有数据,为了解决哈希冲突,往后搜索
						while (newtables[index]._state == EXITS)
						{
                            
							++index;
                            //当index=10的时候,我们回到原点0出
							if (index = _tables.size())
							{
								index = 0;
							}
						}
                        //旧表的数据存储到新表
						newtables[index] = _tables[i];
					}
				}
                //这里相当于将新表变成_tables
				_tables.swap(newtables);//newtables局部变量出了作用域自动析构
			}
			//2.插入
            //把旧表数据遍历完之后,开始插入,计算要插入的数据存储位置
			size_t index = koft(d) % _tables.size();
			HashData cur = _tables[index];
			while (cur._state == EXITS)
			{
                //若当前有数据且大小与插入数据大小一样则不插入返回false
				if (koft(cur._data) == koft(d))
				{
					return false;
				}
                //否则,往后找
				cur = _tables[++index];
				if (index == _tables.size())
				{
					cur = _tables[0];
				}
			}
            //走到这里说明状态为空,或者删除,把数据插入到此刻位置
			cur._data = d;
			cur._state = EXITS;
			++_num;//存储数据增1
			return true;
		}

2.3 查找数据Find()

cpp 复制代码
	HashData* Find(const K& key)
		{
			KeyOfT koft;//仿函数
			size_t index = koft(key) % _tables.size();
            //若此位置的状态不为EMPTY一直往后找
			while (_tables[index]._state != EMPTY)
			{
                //要找的数据与哈希表存储的数据一样大小
				if (koft(_tables[index]._data) == koft(key))
				{
					//状态为存在则找到了,
					if (_tables[index]._state == EXITS)
					{
						return &_tables[index];
					}
                    //我们删除数据的时候,只是把数据的状态置为DELETE
                    //所以要找的数据与哈希表存储的数据一样大小要判断其状态
					else if (_tables[index]._state == DELETE)
					{
						return nullptr;
					}
				}
                //往后遍历
				++index;
				if (index == _tables.size())
				{
					index = 0;
				}
			}
             //走到这里说明没找到
			return nullptr;
		}

2.4 删除数据erase()

cpp 复制代码
bool Erase(const K& key)
		{
            //通过find函数寻找key在哈希表中的位置
			HashData* ret = Find(key);
			if (ret)
			{
                //若ret不为空说明找到了,将状态置为DELETE,数量--
				ret->_state = DELETE;
				--_num;
				return true;
			}
			else
			{ 
               //若ret为nullptr说明没找到
				return false;
			}
		}

3哈希桶(开散列)的实现

3.1哈希桶结点

cpp 复制代码
struct HashNode
	{
       //类似链表存储下一个结点的指针方便后面插入数据
		HashNode<T>* _next;
		T _data;
		//哈希表结点的构造函数
		HashNode(const T& data)
			:_next(nullptr)
			,_data(data)
		{}
	};

3.2哈希桶初步结构

cpp 复制代码
class HashTable
	{
		typedef HashNode<T> Node;
    public:
    //1.
    //2.
    //3.
    private:
		std::vector<Node*> _tables;
		size_t _num = 0;//记录存储数据个数
    }

3.3哈希桶迭代器的封装

3.3.1哈希桶迭代器结构

cpp 复制代码
template<class K, class T, class KeyOfT, class Hash>
struct _HashTableIterator
	{
		typedef HashNode<T> Node;
		typedef _HashTableIterator<K, T, KeyOfT, Hash> Self;
		typedef HashTable<K, T, KeyOfT, Hash> HT;
		//迭代器成员变量
		Node* _node;
		HT* _pht;//hash表指针
        //迭代器构造函数
		_HashTableIterator(Node* node, HT* pht)
			:_node(node)
			,_pht(pht)
		{}
    }

哈希桶迭代器封装了 HashNode<T> Node 和 哈希桶HashTable<K, T, KeyOfT, Hash> HT;

为什么成员变量多加一个 HT* _pht呢,因为迭代器iterator++,后面需要 对数据取模,但并不是所有说都可以取模,当是string 我们就不能取模,所以我们后面需要在HashTable中增加一些仿函数_hash用来取模,我们需要用到_pht->找到取模函数,所以在结构上增加了 HashTable 结点。 class KeyOfT,是为了取出T中key进行比较, class Hash 就是为了加入取模的仿函数。

3.3.2 迭代器* ->重载

cpp 复制代码
//迭代器重载*
		T& operator*()
		{
			return _node->_data;
		}
		//重载->
		T* operator->()
		{
			return &_node->_data;
		}

3.3.3 迭代器++重载

迭代器++,是把一个桶走完了找到下一个桶

cpp 复制代码
//前置++
		Self& operator++()
		{
			//_node->_next 不为空,++就是找下一个
			if (_node->_next)
			{
				_node = _node->_next;
			}
			//_node->_next 为空,++就是找下一个hash桶的第一个
			else
			{
				//仿函数,取值
				KeyOfT koft;
				//_pht->_tables.size()中在类外_tables是私有成员没法访问,用有函数处理
				size_t i = _pht->HashFunc(koft(_node->_data)) % _pht->_tables.size();
				//下一个hash桶
				i++;
				for (; i < _pht->_tables.size(); ++i)
				{
					Node* cur = _pht->_tables[i];
					if (cur)
					{
						_node = cur;
						return *this;
					}
				}
				_node = nullptr;
			}
			return *this;
		}

3.3.4迭代器!=重载

cpp 复制代码
//!=
		bool operator!=(const Self& s)
		{
			return _node != s._node;
		}

取模仿函数的写法

cpp 复制代码
//取模仿函数的创建
	template<class K>
	struct _Hash
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
	//模板全特化,当我们传入string,默认调用这个仿函数
	template<>
	struct _Hash<std::string>
	{
		size_t operator()(const std::string& key)
		{
			size_t hash = 0;
			for (size_t i = 0; i < key.size(); ++i)
			{
				//一种算法保守型比较低
				hash *= 131;
				hash += key[i];
			}
			return hash;
		}
	};

这样我们就初步把迭代器实现好了,接下来我们就可以把迭代器封装到哈希桶中了

cpp 复制代码
class HashTable
	{
		typedef HashNode<T> Node;
	public:
		//友函数,这里加有函数因为迭代器访问了HashTable的私有成员 
		friend struct _HashTableIterator<K, T, KeyOfT, Hash>;
		typedef  _HashTableIterator<K, T, KeyOfT, Hash> iterator;

3.4哈希桶 begin() ,end()

cpp 复制代码
//迭代器起始位置,包含两个变量iterator(_tables[i], this);
		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);
		}

迭代器的begin()的位置就是第一个哈希桶的位置。

3.5哈希桶的析构函数

cpp 复制代码
~HashTable()
		{
			Clear();
		}
       //把链式结点一个一个清理掉
		void Clear()
		{
			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;
			}
		}

3.6哈希桶的Find()

cpp 复制代码
//这个函数是为了使不能取模的数据取模
		size_t HashFunc(const K& key)
		{
			Hash hash;
			return hash(key);
		}
		//查找
		Node* Find(const K& key)
		{
			KeyOfT koft;
			size_t index = HashFunc(koft(key)) % _tables.size();
			Node* cur = _tables[index];
			while (cur)
			{
				if (koft(cur->_data) == koft(key))
				{
					return cur;
				}
				else
				{
					cur = cur->_next;
				}
			}
			return nullptr;
		}

3.7哈希桶的erase()

cpp 复制代码
//删除
		bool Erase(const K& key)
		{
			KeyOfT koft;
			size_t  index= HashFunc(koft(key)) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[index];
			while (cur)
			{
				//删除
				if (koft(cur->_data) == koft(key))
				{
					//删除的元素是第一个
					if (prev == nullptr)
					{
						_tables[index] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					return true;

				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			return false;
		}

3.8哈希表的Insert()

cpp 复制代码
//插入(头插)
		std::pair<iterator, bool> Insert(const T& data)
		{
			KeyOfT koft;
			if (_tables.size() == _num)
			{
				//扩容
				std::vector<Node*> newtables;
				size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				newtables.resize(newsize);
				for (size_t i = 0; i < _tables.size(); ++i)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;
						size_t index = HashFunc(koft(cur->_data)) % newtables.size();
						cur->_next = newtables[index];
						newtables[index] = cur;
						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newtables);
			}
			    //头插,计算数据在表中的映射位置
				size_t index = HashFunc(koft(data)) % _tables.size();
				Node* cur = _tables[index];
				while (cur)
				{
					if (koft(cur->_data) == koft(data))
					{
						return make_pair(iterator(cur, this), false);
					}
					else
					{
						cur = cur->_next;
					}
				}
				//没有重复的头插
				Node* newnode = new Node(data);
				newnode->_next = _tables[index];
				_tables[index] = newnode;
				++_num;
				return make_pair(iterator(newnode, this), true);
		}

此时哈希表结构我们就已经完成接下来实现unordered_set 和 unordered_set 只需要调用模板哈希表。

4 unordered_set 实现

MyUnorderedSet.h

cpp 复制代码
#pragma once
#include"HashTable.h"
#include<string>
#include<iostream>
using namespace std;
using namespace OPEN_HASH;
namespace lt2
{
	template<class K, class Hash = _Hash<K>>
	class unordered_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		//模板之间套用模板
		typedef typename HashTable<K, K, SetKeyOfT, Hash>::iterator iterator;
		iterator begin()
		{
			return _ht.begin();
		}
		iterator end()
		{
			return _ht.end();
		}
		pair<iterator, bool> Insert(const K& k)
		{
			return _ht.Insert(k);
		}
	private:
		HashTable<K, K, SetKeyOfT, Hash> _ht;
	};
	void test_unodered_set()
	{
		unordered_set<int> s;
		s.Insert(1);
		s.Insert(5);
		s.Insert(4);
		s.Insert(2);
		s.Insert(6);
		unordered_set<int>::iterator it = s.begin();
		while (it != s.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}
}

5. unordered_map 实现

MyUnorderedMap.h

cpp 复制代码
#pragma once
#include<iostream>
#include"HashTable.h"
#include<string>
using namespace std;
using namespace OPEN_HASH;
namespace lt1
{
	template<class K,class V,class Hash=_Hash<K>>
	class unordered_map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		//可以理解为要从HashTable<K, pair<K, V>, MapKeyOfT, Hash>找iterator 但
		//HashTable<K, pair<K, V>, MapKeyOfT, Hash>没有实例化找不到,我们加typename表示这是个模板后面找
		typedef typename HashTable<K, pair<K, V>, MapKeyOfT, Hash>::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:
		HashTable<K, pair<K, V>, MapKeyOfT, Hash> _ht;
	};
	void test_unordered_map()
	{
		unordered_map<std::string, std::string> dict;
		dict.Insert(std::make_pair("sort", "排序"));
		dict.Insert(std::make_pair("left", "左边"));
		unordered_map<std::string, std::string>::iterator it = dict.begin();
		while (it != dict.end())
		{
			cout << it->first << "-" << it->second << " ";
			++it;
		}
		cout << endl;
	}

}

附录

HashTable.h

cpp 复制代码
#pragma once
#include<iostream>
using namespace std;
#include<string>
#include<vector>
//开散列hash实现
namespace OPEN_HASH
{
	template<class T>
	//hash结点
	struct HashNode
	{
		HashNode<T>* _next;
		T _data;
		//哈希表结点的构造函数
		HashNode(const T& data)
			:_next(nullptr)
			,_data(data)
		{}
	};
	//Hash迭代器的封装
	template<class K, class T, class KeyOfT, class Hash>
	class HashTable;//前置声明,要不然迭代器往前找不到HashTable
	template<class K,class T,class KeyOfT,class Hash>
	struct _HashTableIterator
	{
		typedef HashNode<T> Node;
		typedef _HashTableIterator<K, T, KeyOfT, Hash> Self;
		typedef HashTable<K, T, KeyOfT, Hash> HT;
		//迭代器成员变量
		Node* _node;
		HT* _pht;//hash表指针
		//迭代器构造函数
		_HashTableIterator(Node* node, HT* pht)
			:_node(node)
			,_pht(pht)
		{}
		//迭代器重载*
		T& operator*()
		{
			return _node->_data;
		}
		//重载->
		T* operator->()
		{
			return &_node->_data;
		}
		//前置++
		Self& operator++()
		{
			//_node->_next 不为空,++就是找下一个
			if (_node->_next)
			{
				_node = _node->_next;
			}
			//_node->_next 为空,++就是找下一个hash桶的第一个
			else
			{
				//仿函数,取值
				KeyOfT koft;
				//_pht->_tables.size()中在类外_tables是私有成员没法访问,用有函数处理
				size_t i = _pht->HashFunc(koft(_node->_data)) % _pht->_tables.size();
				//下一个hash桶
				i++;
				for (; i < _pht->_tables.size(); ++i)
				{
					Node* cur = _pht->_tables[i];
					if (cur)
					{
						_node = cur;
						return *this;
					}
				}
				_node = nullptr;
			}
			return *this;
		}
		//!=
		bool operator!=(const Self& s)
		{
			return _node != s._node;
		}
	};
	//取模仿函数的创建
	template<class K>
	struct _Hash
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
	//模板全特化,当我们传入string,默认调用这个仿函数
	template<>
	struct _Hash<std::string>
	{
		size_t operator()(const std::string& key)
		{
			size_t hash = 0;
			for (size_t i = 0; i < key.size(); ++i)
			{
				//一种算法保守型比较低
				hash *= 131;
				hash += key[i];
			}
			return hash;
		}
	};
	//KeyOfT是取出T的key仿函数,Hash是把数据转换为可以取模的整形
	template<class K,class T,class KeyOfT,class Hash>
	class HashTable
	{
		typedef HashNode<T> Node;
	public:
		//友函数
		friend struct _HashTableIterator<K, T, KeyOfT, Hash>;
		typedef  _HashTableIterator<K, T, KeyOfT, Hash> iterator;
		//迭代器起始位置,包含两个变量iterator(_tables[i], this);
		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()
		{
			Clear();
		}
		void Clear()
		{
			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;
			}
		}
		//这个函数是为了使不能取模的数据取模
		size_t HashFunc(const K& key)
		{
			Hash hash;
			return hash(key);
		}
		//查找
		Node* Find(const K& key)
		{
			KeyOfT koft;
			size_t index = HashFunc(koft(key)) % _tables.size();
			Node* cur = _tables[index];
			while (cur)
			{
				if (koft(cur->_data) == koft(key))
				{
					return cur;
				}
				else
				{
					cur = cur->_next;
				}
			}
			return nullptr;
		}
		//删除
		bool Erase(const K& key)
		{
			KeyOfT koft;
			size_t  index= HashFunc(koft(key)) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[index];
			while (cur)
			{
				//删除
				if (koft(cur->_data) == koft(key))
				{
					//删除的元素是第一个
					if (prev == nullptr)
					{
						_tables[index] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					return true;

				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			return false;
		}
		//插入(头插)
		std::pair<iterator, bool> Insert(const T& data)
		{
			KeyOfT koft;
			if (_tables.size() == _num)
			{
				//扩容
				std::vector<Node*> newtables;
				size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				newtables.resize(newsize);
				for (size_t i = 0; i < _tables.size(); ++i)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;
						size_t index = HashFunc(koft(cur->_data)) % newtables.size();
						cur->_next = newtables[index];
						newtables[index] = cur;
						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newtables);
			}
			    //头插,计算数据在表中的映射位置
				size_t index = HashFunc(koft(data)) % _tables.size();
				Node* cur = _tables[index];
				while (cur)
				{
					if (koft(cur->_data) == koft(data))
					{
						return make_pair(iterator(cur, this), false);
					}
					else
					{
						cur = cur->_next;
					}
				}
				//没有重复的头插
				Node* newnode = new Node(data);
				newnode->_next = _tables[index];
				_tables[index] = newnode;
				++_num;
				return make_pair(iterator(newnode, this), true);
		}
	private:
		std::vector<Node*> _tables;
		size_t _num = 0;//记录存储数据个数
	};
}

test.cpp

cpp 复制代码
#include<iostream>
#include"HashTable.h"
#include"MyUnorderedMap.h"
#include"MyUnorderedSet.h"
using namespace OPEN_HASH;
int main()
{
	lt1::test_unordered_map();
	lt2::test_unodered_set();
	return 0;
}

(͡° ͜ʖ ͡°)麻烦各位看官姥爷点个赞

相关推荐
令狐掌门16 分钟前
C++中间件DDS介绍
c++·中间件·c++ dds
小梁不秃捏2 小时前
深入浅出Java虚拟机(JVM)核心原理
java·开发语言·jvm
我不是程序猿儿3 小时前
【C】识别一份嵌入式工程文件
c语言·开发语言
软件开发技术局4 小时前
撕碎QT面具(8):对控件采用自动增加函数(转到槽)的方式,发现函数不能被调用的解决方案
开发语言·qt
周杰伦fans5 小时前
C#中修饰符
开发语言·c#
yngsqq5 小时前
c# —— StringBuilder 类
java·开发语言
赔罪6 小时前
Python 高级特性-切片
开发语言·python
专注VB编程开发20年6 小时前
除了 EasyXLS,加载和显示.xlsx 格式的excel表格,并支持单元格背景色、边框线颜色和粗细等格式化特性
c++·windows·excel·mfc·xlsx
子豪-中国机器人7 小时前
2月17日c语言框架
c语言·开发语言
夏天的阳光吖7 小时前
C++蓝桥杯基础篇(四)
开发语言·c++·蓝桥杯