用哈希表封装出unordered_set/_map

前提:

①:本博客是对哈希表(开散列)进行封装,因为闭散列不优秀(与库保持一致)

②:哈希表封装出unordered_set/_map和红黑树封装出ste/map是大同小异的,可以先看下:用红黑树封装出set和map -CSDN博客

③:本博客基于手撕哈希表-CSDN博客的基础上讲解,所以哈希表的起始代码如下:

#pragma once
#include<iostream>
#include<vector>
using namespace std;
 
template<class K>
//仿函数 用于得到kv对应的哈希值
//默认的是能直接转换成整形值的 比如int 地址 指针 这种
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};
 
//特化 
//string比较通用,所以特化
//采取DKBR
//使用循环遍历字符串中的每个字符e。
//将字符的ASCII值加到hash上,然后乘以一个常数131。
template<>
struct HashFunc<string>
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto e : s)
		{
			hash += e;
			hash *= 131;
		}
 
		return hash;
	}
};
 
//开散列(哈希桶)的实现
namespace hash_bucket
{
	//这里叫HashNode 因为值存在链表中哦~
	template<class K, class V>
	struct HashNode
	{
		HashNode<K, V>* _next;//只想下一个节点的指针
		pair<K, V> _kv;//存储的键值对
 
		//节点的构造
		HashNode(const pair<K, V>& kv)
			:_next(nullptr)//next默认为空
			, _kv(kv)
		{}
	};
 
	//哈希表类 依旧是一个仿函数用于得到kv对应的哈希值
	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;
	public:
		//哈希表的构造 默认开存储10个空指针的vector
		//相比于闭散列的构造 多了置nullptr这一步
		HashTable()
		{
			_tables.resize(10, nullptr);
			_n = 0;
		}
 
		//析构 用于释放vector下面挂着的链表 
		//外层for循环->遍历vector
		//内层while循环->遍历每一个桶中的节点
		~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)
		{
			//在插入之前,首先调用Find函数检查kv是否已经存在于哈希表中。
			//如果存在,则不需要插入,直接返回false。
			if (Find(kv.first))
				return false;
 
			//仿函数实例化
			Hash hs;
 
			// 负载因子到1就扩容
			if (_n == _tables.size())
			{
				//不再是开一个新哈希表对象了,而是一个新的2倍大小的vector
				//这样效率更高
				vector<Node*> newTables(_tables.size() * 2, nullptr);
 
				//依旧是外层for循环->遍历原来的vector
				//内层while循环->遍历每个桶内的节点
				for (size_t i = 0; i < _tables.size(); i++)
				{
					// 取出旧表中节点,重新计算桶的位置挂到新表桶中
					Node* cur = _tables[i];
					while (cur)
					{
						//保存下一个节点
						Node* next = cur->_next;
 
						// 头插到新表
 
						//计算新位置
						size_t hashi = hs(cur->_kv.first) % newTables.size();
						//让当前节点的 next 指向新表的桶头
						cur->_next = newTables[hashi];
						//把当前节点设为新表的桶头。
						newTables[hashi] = cur;
 
						cur = next;
					}
					//清空旧表的桶
					_tables[i] = nullptr;
				}
 
				//交换两个vector
				_tables.swap(newTables);
			}
 
			//走到这里代表已经扩容完毕 或者不需要扩容
			//直接仿函数对象得到哈希值hashi
			size_t hashi = hs(kv.first) % _tables.size();
			Node* newnode = new Node(kv);
 
			//然后头插即可 切记vector里面放的是节点指针 指向一个桶的首节点
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
 
 
			//插入n记得++
			++_n;
			return true;
		}
 
 
		//查找函数
		//找到返回该节点的指针 反之nullptr
		Node* Find(const K& key)
		{
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}
 
				cur = cur->_next;
			}
 
			return nullptr;
		}
 
		//删除函数
		//不能像闭散列那样find+delete 因为我们单向链表删除后还要链接删除节点的前后节点
		//易错:得看前驱是否为空 即需要以防删除的就是首节点
		bool Erase(const K& key)
		{
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					// 情况1:删除的是中间或尾节点
					if (prev)//前驱不为空 
					{
						prev->_next = cur->_next;// 前驱节点直接跳过当前节点
					}
					//情况2:删除的是头节点
					else//前驱为空
					{
						_tables[hashi] = cur->_next;// 让桶头指向下一个节点
					}
 
					//释放
					delete cur;
 
					//n-1
					--_n;
					return true;
				}
 
				//没找到则继续遍历
				prev = cur;
				cur = cur->_next;
			}
			//遍历结束仍未找到 
			return false;
		}
 
 
		//测试我们写的哈希桶 测试内容如下
		/*负载因子(load factor)
		总桶数量(all bucketSize)
		非空桶数量(bucketSize)
		最长链表长度(maxBucketLen)
		平均链表长度(averageBucketLen)*/
		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("load factor:%lf\n", (double)_n / _tables.size());
			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里面存储的是节点指针了
		vector<Node*> _tables; // 指针数组
		size_t _n;
	};	
}

引入:想要完成封装,我们的哈希表有什么不足?

①:哈希表结构的优化

我们哈希表目前只能存储kv模型;所以结构需要优化,以便既能够成为K模型,又要能成为KV模型

以及我们要做到不论是k模型还是kv模型,都要取得到k值

②:两个仿函数的嵌套适用

我们要先通过仿函数得到k模型或者kv模型中的k值,然后再通过仿函数(哈希函数)得到k值对应的一个整形值;这是和红黑树封装的不同,红黑树只需要得到k值即可。

③:迭代器的实现

④:unordered_map的[ ]的实现

一:优化哈希表的结构

A:要想即能创建出k模型的unordered_set和kv模型的unordered_map:

和红黑树类似,哈希表也是从第二个模板参数进行操作,就能达到效果

B:要想无论是k模型还是kv模型,都要取得到k值

从仿函数入手即可

相关细节不再赘述,红黑树封装博客里面有类似的详细讲解:用红黑树封装出set和map -CSDN博客

直接看代码:

unordered_set代码:

#include"HashTable.h"


	template<class K, class Hash = HashFunc<K>>//外界调用所需参数
	class unordered_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:

	private://创建一个哈希表对象
		hash_bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht;
	};



unordered_map代码:

    #include"HashTable.h"

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

	
		
	private://创建一个哈希表对象
		hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
	};

解释:对哈希表的第四个参数的解释

       ①: 创建哈希表中的第四个参数Hash,是实现在HashTable.h中的,且unordered_set和unordered_map文件中在包含了HashTable.h后,都是在模版中的Hash给上缺省值的,所以我们HashTable.h中,哈希表的模版中的Hash参数不需要再给缺省值

        ②:为什么在unordered_set/map中处理这个参数? 因为,使用者不可能直接用哈希表而是用unordered_set或unordered_map,所以一般是不需要对unordered_set/map参数中的哈希函数进行传参的,只有k值是特殊类型的时候,才需要自己实现哈希函数进行手动传参

所以哈希表的起始状态如下:

//哈希函数 以及模版参数为string的特化处理
template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

//特化
template<>
struct HashFunc<string>
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto e : s)
		{
			hash += e;
			hash *= 131;
		}

		return hash;
	}
};


namespace hash_bucket
{
	// T -> K
	// T -> pair<K, V>

	//哈希节点类 哈希桶中都是节点 所以叫节点类
	template<class T>
	struct HashNode
	{
		HashNode<T>* _next;
		T _data;

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


	//哈希表类
	template<class K, class T, class KeyOfT, class Hash>//Hash不需要缺省值
	class HashTable
	{

		//节点类的缩写              
		typedef HashNode<T> Node;
	public:

	private:
		vector<Node*> _tables; // 指针数组
		size_t _n;
	};
}

        到这里,我们的哈希表既能创建出k模型的unordered_set和kv模型的unordered_map;也能无论是k模型还是kv模型,都要取得到k值;通过两个仿函数就可以办到~

二:两个仿函数的嵌套使用

        我们的函数,也要进行改变,不再是之前的写死了对pair类型进行操作,现在获取到的是T类型的data值,我们要先对data进行取出其中的k,然后再用仿函数让k得到一个整形

所以插入 查找 删除函数改动如下:

//插入函数
		pair<iterator, bool> Insert(const T& data)
		{
			KeyOfT kot;

			iterator it = Find(kot(data));
			if (it != end())
				return make_pair(it, false);


			//仿函数对象的实例化
			Hash hs;

			// 负载因子到1就扩容
			if (_n == _tables.size())
			{
				vector<Node*> newTables(GetNextPrime(_tables.size()), nullptr);
				for (size_t i = 0; i < _tables.size(); i++)
				{
					// 取出旧表中节点,重新计算挂到新表桶中
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;

						// 头插到新表
						size_t hashi = hs(kot(cur->_data)) % newTables.size();
						cur->_next = newTables[hashi];
						newTables[hashi] = cur;

						cur = next;
					}

					_tables[i] = nullptr;
				}

				_tables.swap(newTables);
			}

			size_t hashi = hs(kot(data)) % _tables.size();
			Node* newnode = new Node(data);

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

			++_n;
			return make_pair(iterator(newnode, this), true);
		}

		//查找函数
		iterator Find(const K& key)
		{
			KeyOfT kot;
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return iterator(cur, this);
				}

				cur = cur->_next;
			}

			return iterator(nullptr, this);
		}

		//删除函数
		bool Erase(const K& key)
		{
			KeyOfT kot;
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					// 删除
					if (prev)
					{
						prev->_next = cur->_next;
					}
					else
					{
						_tables[hashi] = cur->_next;
					}

					delete cur;

					--_n;
					return true;
				}

				prev = cur;
				cur = cur->_next;
			}

			return false;
		}

解释:

①:仿函数的实例化

//取出data里面的k值
KeyOfT kot; 

//将k值转换为一个整形值
Hash hs; 

②:仿函数嵌套适用:

hs(kot(data))

这就叫仿函数的嵌套使用,先拿出data里面的k值,然后得到k值对应的整形值 

③:insert函数的返回值,和红黑树一样,是为了unordered_map的[ ]所准备的

④:扩容中用到了一个GetNextPrime函数,先别管,后面会讲

三:迭代器的实现

哈希表的迭代器的实现是一个重点,和以往的任何的迭代器实现都不同,以前的实现,要么是直接复用的指针(如底层连续的vector),要么就是对一个节点指针进行封装,去重载++ * != 等操作符;

哈希表的迭代器的问题:

Q:你对一个节点指针进行了封装,虽然可以* ++ !=....重载,但是一个桶走完了,怎么找到下一个桶?

A:所以哈希表的迭代器不再只对节点指针封装,且还要对哈希表指针封装,也就是有两个成员变量

注意:迭代器博主只实现了正向非const迭代器,其余的大同小异,不再赘述

1:迭代器代码:

    //哈希桶的迭代器类
	template<class K, class T, class KeyOfT, class Hash>
	struct __HTIterator
	{
		typedef HashNode<T> Node;
		typedef HashTable<K, T, KeyOfT, Hash> HT;
		typedef __HTIterator<K, T, KeyOfT, Hash> Self;

		//两个成员变量
		Node* _node;
		HT* _ht;

		//迭代器的构造
		__HTIterator(Node* node, HT* ht)
			:_node(node)
			, _ht(ht)
		{}

		//*重载
		T& operator*()
		{
			return _node->_data;
		}

		//->的重载
		T* operator->()
		{
			return &_node->_data;
		}

		//++的重载
		Self& operator++()
		{
			if (_node->_next)
			{
				// 当前桶还是节点
				_node = _node->_next;
			}
			else
			{
				// 当前桶走完了,找下一个桶
				KeyOfT kot;
				Hash hs;
				size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();
				// 找下一个桶
				hashi++;
				while (hashi < _ht->_tables.size())
				{
					if (_ht->_tables[hashi])
					{
						_node = _ht->_tables[hashi];
						break;
					}

					hashi++;
				}

				// 后面没有桶了
				if (hashi == _ht->_tables.size())
				{
					_node = nullptr;
				}
			}

			return *this;
		}

		//!=的重载
		bool operator!=(const Self& s)
		{
			return _node != s._node;
		}
	};

2:迭代器代码解释:

①:成员变量

Node* _node;  // 当前指向的哈希节点
HT* _ht;     // 指向所属的哈希表

②:关键类型定义

typedef HashNode<T> Node;
typedef HashTable<K, T, KeyOfT, Hash> HT;
typedef __HTIterator<K, T, KeyOfT, Hash> Self;

Self:迭代器自身的类型,简化返回值类型声明(如 operator++ 返回 Self&)。 

③:迭代器的核心操作

a:解引用操作 operator*

T* operator->() 
{
    return &_node->_data;  // 返回节点数据的指针
}

b:成员访问操作 operator->

T* operator->() 
{
    return &_node->_data;  // 返回节点数据的指针
}

c: 不等比较 operator!=

bool operator!=(const Self& s) 
{
    return _node != s._node;  // 比较两个迭代器是否指向同一节点
}

d:最关键的 operator++(跨桶遍历)

Self& operator++() 
{
    //情况1:当前桶还有下一个节点
    if (_node->_next) 
    {
       
        _node = _node->_next;
    } 
    
    //来到这里 代表是情况2
    //情况2:当前桶已遍历完,需跳转到下一个非空桶
    else 
    {
        //所以需要先计算当前节点所在的桶索引 hashi!

        KeyOfT kot;
        Hash hs;
        //hashi即当前节点所在的桶索引
        size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();

        
        hashi++;  // 从下一个桶开始找
        
        //hashi得一直++,直到找到一个不为空的桶(为空代表vector此槽位是nullptr)
        while (hashi < _ht->_tables.size()) 
        {
            if (_ht->_tables[hashi]) // 找到非空桶
            {
                _node = _ht->_tables[hashi];  // 指向该桶的首节点
                break;
            }
            hashi++;
        }

        //hashi一直++ 直到vector遍历完了 都没发现非空桶 
        //则将迭代器置为end()(因为end()就是nullptr)
        if (hashi == _ht->_tables.size()) 
        {
            _node = nullptr;
        }
    }
    return *this;
}

 解释:代码逻辑转换为图片如下

四:map的[ ]的实现 

在unordered_map中进行以下实现即可,和红黑树类似,不再赘述:

        //[ ]函数
		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = insert(make_pair(key, V()));
			return ret.first->second;
		}

 

五:总代码

①:HashTable.h

#pragma once
#include<iostream>
#include<vector>
#include<unordered_set>
#include<unordered_map>
#include<set>
using namespace std;

//哈希函数 以及模版参数为string的特化处理
template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

//特化
template<>
struct HashFunc<string>
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto e : s)
		{
			hash += e;
			hash *= 131;
		}

		return hash;
	}
};


//开散列的域 
//开散列 也叫哈希桶
namespace hash_bucket
{
	// T -> K
	// T -> pair<K, V>

	//哈希节点类 哈希桶中都是节点 所以叫节点类
	template<class T>
	struct HashNode
	{
		HashNode<T>* _next;
		T _data;

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

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

	//哈希桶的迭代器类
	template<class K, class T, class KeyOfT, class Hash>
	struct __HTIterator
	{
		typedef HashNode<T> Node;
		typedef HashTable<K, T, KeyOfT, Hash> HT;
		typedef __HTIterator<K, T, KeyOfT, Hash> Self;

		//两个成员变量
		Node* _node;
		HT* _ht;

		//迭代器的构造
		__HTIterator(Node* node, HT* ht)
			:_node(node)
			, _ht(ht)
		{}

		//*重载
		T& operator*()
		{
			return _node->_data;
		}

		//->的重载
		T* operator->()
		{
			return &_node->_data;
		}

		//++的重载
		Self& operator++()
		{
			if (_node->_next)
			{
				// 当前桶还剩节点
				_node = _node->_next;
			}
			else
			{
				// 当前桶走完了,找下一个桶
				KeyOfT kot;
				Hash hs;
				size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();
				// 找下一个桶
				hashi++;
				while (hashi < _ht->_tables.size())
				{
					if (_ht->_tables[hashi])
					{
						_node = _ht->_tables[hashi];
						break;
					}

					hashi++;
				}

				// 后面没有桶了
				if (hashi == _ht->_tables.size())
				{
					_node = nullptr;
				}
			}

			return *this;
		}

		//!=的重载
		bool operator!=(const Self& s)
		{
			return _node != s._node;
		}
	};

	//哈希表类
	template<class K, class T, class KeyOfT, class Hash>// KeyOfT用于取出T中的k值 Hash则是得到这个取出的k值对应的整形
	class HashTable
	{
		template<class K, class T, class KeyOfT, class Hash>
		friend struct __HTIterator;

		//节点类的缩写              
		typedef HashNode<T> Node;
	public:
		typedef __HTIterator<K, T, KeyOfT, Hash> iterator;


		//迭代器的两个函数
		//begin()函数
		iterator begin()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				// 找到第一个桶的第一个节点
				if (_tables[i])
				{
					return iterator(_tables[i], this);
				}
			}

			return end();
		}
		//end()函数
		iterator end()
		{
			return iterator(nullptr, this);
		}

		//构造函数
		HashTable()
		{
			_tables.resize(10, nullptr);
			_n = 0;
		}

		//析构函数
		~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;
			}
		}


		//获取本次增容后哈希表的大小 空间呈素数增长
		size_t GetNextPrime(size_t prime)
		{
			const int PRIMECOUNT = 28;
			//素数序列
			const size_t primeList[PRIMECOUNT] =
			{
				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
			};
			size_t i = 0;
			for (i = 0; i < PRIMECOUNT; i++)
			{
				if (primeList[i] > prime)
					return primeList[i];
			}
			return primeList[i];//逻辑返回
		}

		//插入函数
		pair<iterator, bool> Insert(const T& data)
		{
			KeyOfT kot;

			iterator it = Find(kot(data));
			if (it != end())
				return make_pair(it, false);


			//仿函数对象的实例化
			Hash hs;

			// 负载因子到1就扩容
			if (_n == _tables.size())
			{
				vector<Node*> newTables(GetNextPrime(_tables.size()), nullptr);
				for (size_t i = 0; i < _tables.size(); i++)
				{
					// 取出旧表中节点,重新计算挂到新表桶中
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;

						// 头插到新表
						size_t hashi = hs(kot(cur->_data)) % newTables.size();
						cur->_next = newTables[hashi];
						newTables[hashi] = cur;

						cur = next;
					}

					_tables[i] = nullptr;
				}

				_tables.swap(newTables);
			}

			size_t hashi = hs(kot(data)) % _tables.size();
			Node* newnode = new Node(data);

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

			++_n;
			return make_pair(iterator(newnode, this), true);
		}

		//查找函数
		iterator Find(const K& key)
		{
			KeyOfT kot;
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return iterator(cur, this);
				}

				cur = cur->_next;
			}

			return iterator(nullptr, this);
		}

		//删除函数
		bool Erase(const K& key)
		{
			KeyOfT kot;
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					// 删除
					if (prev)
					{
						prev->_next = cur->_next;
					}
					else
					{
						_tables[hashi] = cur->_next;
					}

					delete cur;

					--_n;
					return true;
				}

				prev = cur;
				cur = cur->_next;
			}

			return false;
		}
	private:
		vector<Node*> _tables; // 指针数组
		size_t _n;
	};
}

②:My_unordered_set

#pragma once
#include"HashTable.h"

namespace bit
{
	template<class K, class Hash = HashFunc<K>>
	class unordered_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public://复用哈希表类的迭代器
		typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::iterator iterator;

		//复用哈希表的begin函数
		iterator begin()
		{
			return _ht.begin();
		}

		//复用哈希表的end函数
		iterator end()
		{
			return _ht.end();
		}

		//复用哈希表的插入函数
		pair<iterator, bool> insert(const K& key)
		{
			return _ht.Insert(key);
		}

		//复用哈希表的查找函数
		iterator Find(const K& key)
		{
			return _ht.Find(key);
		}

		//复用哈希表的删除函数
		bool erase(const K& key)
		{
			return _ht.Eease(key);
		}

	private://创建一个哈希表对象
		hash_bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht;
	};

	//测试set 体现迭代器遍历出来是无序的 且set的k值不能被修改
	void test_set1()
	{
		unordered_set<int> us;
		us.insert(3);
		us.insert(1);
		us.insert(5);
		us.insert(15);
		us.insert(45);
		us.insert(7);

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

		for (auto e : us)
		{
			cout << e << " ";
		}
		cout << endl;
	}

}

③:My_unordered_map

#pragma once
#include"HashTable.h"

namespace bit
{

	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 hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::iterator iterator;

		//复用哈希表的begin()函数
		iterator begin()
		{
			return _ht.begin();
		}
		//复用哈希表的end()函数
		iterator end()
		{
			return _ht.end();
		}

		//复用哈希表的插入函数
		pair<iterator, bool> insert(const pair<K, V>& kv)
		{
			return _ht.Insert(kv);
		}

		//复用哈希表的查找函数
		iterator Find(const K& key)
		{
			return _ht.Find(key);
		}

		//复用哈希表的删除函数
		bool erase(const K& key)
		{
			return _ht.Eease(key);
		}

		//[ ]函数
		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = insert(make_pair(key, V()));
			return ret.first->second;
		}
	private://创建一个哈希表对象
		hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
	};

	//测试map 体现迭代器遍历出来是无序的 且map的k值不能被修改 v值可以修改
	void test_map1()
	{
		unordered_map<string, string> dict;
		dict.insert(make_pair("sort", "排序"));
		dict.insert(make_pair("left", "左面"));
		dict.insert(make_pair("right", "右面"));

		for (auto& kv : dict)
		{
			//kv.first += 'x';
			kv.second += 'y';

			cout << kv.first << ":" << kv.second << endl;
		}
	}

	//测试map的[]
	void test_map2()
	{
		string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
					 "苹果", "香蕉", "苹果", "西瓜", "香蕉", "草莓" };
		unordered_map<string, int> countMap;
		for (auto& e : arr)
		{

			countMap[e]++;
		}

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

}

④:test.cpp 

#define _CRT_SECURE_NO_WARNINGS 1

#include"MyOrderedMap.h"
#include"MyOrderedSet.h"

int main()
{


	//测试封装后的OrderedSet和OrderedMap 体现无序性
	bit::test_set1();
	bit::test_map1();
	//测试OrderedMap的[]
	bit::test_map2();


	return 0;
}

⑤:代码整合

当多份独立可运行的代码合并到一个项目中时,需要解决一些问题:

a:创建哈希表时候的第二个参数的写法

//My_unordered_set中:
const K

//My_unordered_map中:
pair<const K, V>

解释:这样写,对于My_unordered_set,k不能改;对于My_unordered_map ,k不能改,v可以改

和库保持一致,符合这种数据结构的特性

b:在迭代器之前要前置声明一下哈希表类,注意写法:

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

解释:

Q:为什么在 迭代器之前要前置声明哈希表类?

A:因为迭代器类中又需要用到 HashTable 的类型(如 typedef HashTable<K, T, KeyOfT, Hash> HT;);这样迭代器类才能安全地引用 HashTable,而不会报错“未定义的类型”。

c:哈希表类中要声明迭代器类为友元:

        template<class K, class T, class KeyOfT, class Hash>
		friend struct __HTIterator;

Q: 为什么要在哈希表类中要声明迭代器类为友元?

A:因为迭代器需要访问 HashTable 的 私有成员(如 _tables 和 _n),但默认情况下,外部类(包括迭代器)无法访问私有成员;

例如,operator++ 中需要计算哈希桶位置:

size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();  // 访问 _tables

 如果 _tables 是 private,且迭代器不是友元,这段代码会编译失败。

Q:为什么不能直接把 _tables 改成 public

A:

  • 破坏封装性,外部任何代码都可以随意修改哈希表内部结构,不安全。

  • 友元是一种 受控的暴露,只允许特定的类(如迭代器)访问私有成员

d:GetNextPrime函数的作用

GetNextPrime 是哈希表扩容时用于 计算新容量 的函数,其核心目的是 返回一个比当前容量大的素数,作为哈希表的新桶数组大小。这是为了解决哈希表扩容时的效率问题,并减少哈希冲突。

因为,经过前辈研究,素数扩容是最好的,会有效的减少哈希冲突!

六:测试代码

①:测试My_unordered_set

void test_set1()
	{
		unordered_set<int> us;
		us.insert(3);
		us.insert(1);
		us.insert(5);
		us.insert(15);
		us.insert(45);
		us.insert(7);

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

		for (auto e : us)
		{
			cout << e << " ";
		}
		cout << endl;
	}

运行结果:

解释:迭代器和范围for均正常,且符合无序的预期

②:测试My_unordered_map

void test_map1()
	{
		unordered_map<string, string> dict;
		dict.insert(make_pair("sort", "排序"));
		dict.insert(make_pair("left", "左面"));
		dict.insert(make_pair("right", "右面"));

		for (auto& kv : dict)
		{
			//kv.first += 'x';
			kv.second += 'y';

			cout << kv.first << ":" << kv.second << endl;
		}
	}

运行结果:

解释:符合无序的预期,且k值不能改变,但可以对v值进行了改变,正确

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

			countMap[e]++;
		}

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

运行结果:

 

解释:符合预期

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值