哈希表底层探索

Posted ych9527

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了哈希表底层探索相关的知识,希望对你有一定的参考价值。

1.unordered_ map、unordered_set

1.1介绍

在C++98之中提供了四个关联式容器,分别是:map、set、multimap、multiset,他们的底层是红黑树,查询时候的效率可以达到longN。但是当数据量非常大的时候,也不是很理想。于是在C++11之中,又增加了四个用法一样的关联式容器,只是底层的实现采用的是哈希表。这四个关联式容器分别是:unordered_map、unordered_set、unordered_multimap、unordered_multiset

它们的底层用的是哈希桶,因此迭代器是单向的

1.2效率对比

#include <iostream>
#include <set>
using namespace std;
#include <time.h>
#include <unordered_set>

int main()
{
	
	srand((unsigned)time(NULL));

	set<int> RBset;
	unordered_set<int> Hash;

	int begin1=clock();
	for (int i = 0; i < 500000;i++)
	{
		RBset.insert(rand() % 1000);
	}
	int end1 = clock();

	int begin2 = clock();
	for (int i = 0; i < 500000; i++)
	{
		Hash.insert(rand() % 1000);
	}
	int end2 = clock();


	cout << "set和unordered_set效率对比" << endl << endl;
	cout << "红黑树set:" << end1 - begin1 << endl << endl;
	cout << "哈希et:" << end2 - begin2 << endl;

	system("pause");
	return 0;
}

image-20210608101210628

1.3实战演练

哈希常见面试题

2.哈希

2.1哈希的概念

map和set底层用的都是红黑树,进行元素的查找和插入都需要经过logN的时间复杂度,即需要通过元素之间的多次比较才能够完成。

理想的搜索方法是,不经过任何比较,一次性直接从表中获取元素。哈希就是这种设计理念,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立起对应的映射关系,在O(1)的时间内,插入、获取元素

向结构中插入元素:
根据待插入元素的关键码,用哈希函数计算出对应的存储位置,并且按照此位置进程存放

搜索结构中的元素:
对元素关键码进行计算,将函数返回值作为元素的存储位置进行搜索,如果对应的位置的元素与搜索元素可以匹配,则搜索成功

这种方法即为哈希(散列)方法,哈希方法中使用的函数称之为哈希函数,构造出来的结构称为哈希表

2.2哈希函数

2.2.1设计原则

哈希函数设计的不合理,会导致哈希冲突,哈希函数一般按照以下几点原则进行设计:

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

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

3.哈希函数应该比较简单

2.2.2常见哈希函数

2.2.2.1常用哈希函数

1.直接定址法(常用)

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

优点:简单、均匀
缺点:实现需要知道关键字的分布情况,并且只适合查找比较小,且连续分布的情况

场景:

适用场景:查找字符串中,第一次出现的单词:构建一个数组 hash[ch-‘a’] 即为对应的地址

不适用场景:给一批数据, 1 5 8 100000 像这数据跨度大,数据元素不连续,很容易造成空间浪费

2.除留余数法(常用)

设散列表中允许的地址数为m,通常是取一个不大于m,但是最接近或者等于m的质数num,作为除数,按照哈希函数进行计算hash(key)= key%num, 将关键码转换成哈希地址

除留余数法,最好模一个素数:

const int PRIMECOUNT = 28;
	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, 429496729ul
	};
	
	size_t GetNextPrime(size_t prime)
	{
		size_t i = 0;
		for (; i <PRIMECOUNT; ++i){
			if (primeList[i] > prime)
				return primeList[i];
		}

		return primeList[i];
	}

2.2.2.2 不常用哈希函数

1.平方取中法

hash(key)=key*key -> 然后取函数返回值的中间的几位,作为哈希地址
比如 25^2 = 625 取中间的一位 2 作为哈希地址

比较适合不知道关键字的分布,而位数又不是很大的情况

2.折叠法

将关键字从左到右分割成位数相等的几部分(最后一部分可以短些),然后将这几部分叠加求和,并且按照散列表长度,取最后几位作为散列地址

适用于不知道关键字分布,关键字位数比较多的情况

3.随机数法

选取一个随机函数,取关键字的随机函数值,作为它的哈希地址,hash(key) = random(key),random为随机函数

通常用于关键字长度不等的情况

4.数学分析法

通过实现分析关键字,来获取哈希地址

比如用每个人的手机号码充当关键字,如果采用前三位作为哈希地址,那么冲突的概率是非常大的。如果采用的是中间3位那么冲突的概率要小很多

常用于处理关键字位数比较大的情况,且事前知道关键字的分布和关键字的若干位的分布情况

2.3哈希冲突

image-20210608101302312

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

3.闭散列

闭散列也叫做开放地址法,当发生哈希冲突的时候,如果哈希表未被填满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置的"下一个"空位置中去,寻找下一个空位置的方法有线性探测法和二次探测法

3.1线性探测

从发生冲突的位置开始,依次向后探测,直到寻找到下一个位置为止

优点:实现非常简单
缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据"堆积",即不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要进行多次比较,导致搜索效率降低

image-20210608111205854

插入:

通过哈希函数插入元素在哈希表中的位置,如果发生了哈希冲突,则使用线性探测寻找下一个空位置插入元素

image-20210608104828926

删除:

采用闭散列处理哈希冲突时,不能随便删除哈希表中已有的元素,如果直接删除元素,会影响其他元素的搜索
因此线性探测采用标记的伪删除法来删除下一个元素

image-20210608105445048

3.2闭散列扩容-载荷因子

散列表的载荷因子定义为 α = 填入表中的元素 / 散列表的长度

α是散列表装满程度的标志因子,α越大表明装入表中的元素越多,产生冲突的可能性也就越大,反之填入表中的元素越少,冲突可能性越低,空间利用率也就越低

闭散列:一般将载荷因子控制在 0.7-0.8以下,超过0.8查表时候的缓存不中率会按照指数曲线上升(哈希可能性冲突越大),因此一般hash库中,都将α设置在0.8以下。 闭散列,千万不能为满,否则在插入的时候会陷入死循环

开散列/哈希桶:一般将载荷因子控制在1。超过1,那么链表就挂得越长,效率也就越低

3.3二次探测

线性探测的缺陷是产生哈希冲突,容易导致冲突的数据堆积在一起,这是因为线性探测是逐个的找下一个空位置
二次探测为了缓解这种问题(不是解决),对下一个空位置的查找进行了改进(跳跃式查找):

POS = (H+i^2)%m || POS = (H - i^2)%m
其中:i=1、2、3…
H是发生哈希冲突的位置
m是哈希表的大小

image-20210608122036001

3.4平均查找长度

先将值放入哈希表之中

平均查找次数 = 查找每个值的次数总和/值的个数

3.5模拟实现

设计过程:

1.整体结构:

在这里插入图片描述

2.扩容实现:

image-20210610172502295

3.算法技巧
在这里插入图片描述

4.封装成set、map的代码和验证

image-20210610172657663

5.代码和实验效果

闭散列代码:

#ifndef _HASH_HPP_
#define _HASH_HPP_

using namespace std;
#include <iostream>
#include <vector>
#include <assert.h>
#include <string> 


//闭散列
//set<K> -> HashTable<K,K>
//map<K,V> -> HashTable<K,pair<K,V>>
namespace YCH_CLOSE_HASH
{

	enum State
	{
		EMPTY,//空
		DELETE,//存在
		EXIST//删除
	};

	template<class T>
	struct HashNode//存储的节点
	{
		State _state = EMPTY;//节点状态,默认为空状态(缺省值)
		T _t;//节点的值

		HashNode(const T&t=T())
			:_t(t)
		{}

	};

	//迭代器的构造

	//提前声明哈希表
	template <class K, class T, class KeyofT, class Hash>//声明和定义不能同时候缺省参数
	class HashTable;

	template<class K, class T, class KeyofT, class Hash>
	struct  HashIterator
	{
		typedef HashIterator<K, T, KeyofT, Hash> Self;
		typedef HashNode<T> Node;
		typedef HashTable<K, T, KeyofT, Hash> HashTable;

		Node *_node;
		HashTable *_pht;//迭代器里面还需要包括哈希表


		HashIterator(Node* node, HashTable *pth)
			:_node(node)
			, _pht(pth)
		{}

		T& operator*()
		{
			assert(_node != nullptr);
			return _node->_t;
		}

		T* operator ->()
		{
			assert(_node != nullptr);
			return &(_node->_t);
		}

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

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

		Self& operator++()//寻找当前位置的后面一个有元素的位置
		{
			//先找到当前节点所在位置,然后往后寻找下一个节点的位置
			KeyofT kf;

			size_t begin = _pht->HashFunc(kf(_node->_t));//得到映射的哈希位置
			//不清楚是否发生冲突因此还要再次寻找
			size_t index = begin;
			size_t i = 1;
			int flag = 1;

			while (_pht->_tables[index]._state != EMPTY)//等于空就停止,这也是为什么要用HashNode的原因,直接判断,如果删除了,也会出现空
			{
				if (_pht->_tables[index]._state == EXIST&&kf(_node->_t) == kf(_pht->_tables[index]._t))//存在且K值对应
				{
					break;
				}
				
				index = (begin + i*i*flag) % _pht->_tables.size();//二次探测

				if (flag == -1)
				{
					i++;//增大值
					flag = 1;
				}
				else
				{
					flag = -1;//变换方向
				}
			}

			//此时index的位置就是第一个当前位置的数据,然后往后遍历整张表,输出元素
			for (int i = index+1; i < _pht->_tables.size(); i++)
			{
				if (_pht->_tables[i]._state == EXIST)
				{
					/*_node->_t = _pht->_tables[i]._t; //BUG,这是将原来节点里面的值也给更改了
					_node->_state = _pht->_tables[i]._state;*/
					
					_node = &(_pht->_tables[i]);
					return *this;
				}
			}

			//return _pht->End();这样不行,没有接受值,只是返回了一个下一个位置的迭代器
			_node = nullptr;
			return *this;
		}

		Self operator++(int)//后置++
		{
			//先找到当前节点所在位置,然后往后寻找下一个节点的位置
			KeyofT kf;

			size_t begin = _pht->HashFunc(kf(_node->_t));//得到映射的哈希位置
			//不清楚是否发生冲突因此还要再次寻找
			size_t index = begin;
			size_t i = 1;
			int flag = 1;

			while (_pht->_tables[index]._state != EMPTY)//等于空就停止,这也是为什么要用HashNode的原因,直接判断,如果删除了,也会出现空
			{
				if (_pht->_tables[index]._state == EXIST&&kf(_node->_t) == kf(_pht->_tables[index]._t))//存在且K值对应
				{
					break;
				}

				index = (begin + i*i*flag) % _pht->_tables.size();//二次探测

				if (flag == -1)
				{
					i++;//增大值
					flag = 1;
				}
				else
				{
					flag = -1;//变换方向
				}
			}

			//此时index的位置就是第一个当前位置的数据,然后往后遍历整张表,输出元素

			Node *copy_node = _node;//保存一份
			for (int i = index + 1; i < _pht->_tables.size(); i++)
			{
				if (_pht->_tables[i]._state == EXIST)
				{
					/*_node->_t = _pht->_tables[i]._t; //BUG,这是将原来节点里面的值也给更改了
					_node->_state = _pht->_tables[i]._state;*/

					_node = &(_pht->_tables[i]);


					return Self(copy_node, _pht);
				}
			}

			//return _pht->End();这样不行,迭代器本身没有改变,只是返回了一个下一个位置的迭代器
			_node = nullptr;
			return Self(copy_node, _pht);
		}


	};

	//哈希表的构造


	size_t GetNextPrime(size_t prime)
	{
		static 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, 429496729ul
		};

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

		return primeList[i];
	}

	template<class K, class T, class KeyofT, class Hash>//默认给int进行比较
	class HashTable
	{
	public:
		//构造迭代器
		typedef HashIterator<K, T, KeyofT, Hash> Iterator;
		friend  Iterator; //<=>HashIterator<K, T, KeyofT, Hash>;//将迭代器声明为,哈希表的友元类,即可以访问哈希表的私有成员

		Iterator Begin()
		{
			for (int i = 0; i < _tables.size(); i++)//找到第一个不为空的值
			{
				if (_tables[i]._state == EXIST)
					return Iterator(&_tables[i], this);
			}
			return End();
		}

		Iterator End()
		{
			return Iterator(nullptr, this);
		}

		//构造插入函数

		size_t HashFunc(const K& key)//哈希函数
		{
			Hash hf;
			return hf(key) % _tables.size();
		}

		pair<Iterator,bool> Insert(const T & t)
		{
			//判断要插入的元素是否已经存在
			KeyofT kf;
			Iterator ret = Find(kf(t));
			if (ret!=End())//已经存在了,multiset/multimap则不需要
				return make_pair(ret,false);
				
			//进行扩容检测
			if (_size == 0 || (_size / _tables.size() * 10 > 7))//当前个数为0或者载荷因子超过了,则进行扩容
			{
				//size_t newsize = _size == 0 ? 10 : 2 * _tables.size();//初始化给10,后续扩容两倍

				//选取素数
				size_t newsize = GetNextPrime(_tables.size());

				//扩容之后,需要重新计算元素的位置

				HashTable<K, T, KeyofT, Hash> newtable;
				newtable._tables.resize(newsize);

				for (auto&e : _tables)
				{
					if (e._state == EXIST)
						newtable.Insert(e._t);
				}
				_tables.swap(newtable._tables);//进行交换
			}


			//查找插入的位置

			//KeyofT kf;//获取元素类型
			//Hash hf;//将元素转为整形

			size_t begin = HashFunc(kf(t));//获取映射位置
			size_t index = begin;
			size_t i = 1;
			int flag = 1;
			while (_tables[index]._state == EXIST)//发生冲突,则继续寻找
			{
				index = (begin + i*i*flag) % _tables.size();//二次探测

				if (flag == -1)
				{
					i++;//增大值
					flag = 1;
				}
				else
				{
					flag = -1;//变换方向
				}
			}

			//此时已经找到位置了,进行元素的添加
			
			_tables[index]._t = t;
			_tables[index]._state = EXIST;
			_size++;

			return make_pair(Iterator(&_tables[index],this),true);
		}

		Iterator Find(const K& key)//查找的时候需要注意,查找的值不一定存在
		{
			if (_size == 0)//为空
				return Iterator(nullptr,this);

			//Hash hf;//转整形
			KeyofT kf;//拿K值

			size_t begin = HashFunc(key);//转为整形,获取映射位置.
			size_t index = begin;
			size_t i = 1;
			int flag = 1;

			while (_tables[index]._state != EMPTY)//等于空就停止,这也是为什么要用HashNode的原因,直接判断,如果删除了,也会出现空
			{
				if (_tables[index]._state == EXIST&&key == kf(_tables[index]._t))//存在且K值对应
				{
					return Iterator(&_tables[index],this);
				}

				index = (begin + i*i*flag) % _tables.size();//二次探测

				if (flag == -1)
				{
					i++;//增大值
					flag = 1;
				}
				else
				{
					flag = -1;//变换方向
				}
			}
			//当前值不存在
			return End();
		}

		bool Erase(const K& key)//先找到再删除
		{
			HashNode<T>* node = Find(key);
			if (node)
			{
				node->_state = DELETE;//伪删除
				_size--;
				return true;
			}
			return false;
		}


	private:
		vector<HashNode<T>> _tables;//底层结构
		size_t _size = 0;//存储的数据的个数
	};
};


#endif

封装:

map封装:

#pragma once
#include "hash.hpp"


namespace YCH_MAP
{
	//内置哈希转换函数 <-> 常用int 和 string 
	//如果K类型不支持取模,就需要配上一个仿函数来进行使用
	template<class K>
	struct Hash
	{
		size_t operator() (const K&key)
		{
			return key;
		}
	};

	//string类型常用,进行特化
	template<>
	struct Hash<string>
	{
		size_t operator() (const string &key)
		{
			size_t count = 0;
			for (auto&e : key)
			{
				count = count * 131 + e;// 字符串转整形求哈希地址常用值131,可以减少冲突
			}
			return count;
		}
	};

	template<class K, class V, class hash = Hash<K>>
	class ych_unordered_map
	{

	private:
		struct map_KeyofT
		{
			const K& operator()(const pair<const K, V>& kv)
			{
				return kv.first;
			}
		};

	public:
		typedef typename YCH_CLOSE_HASH::HashTable<K, pair<K, V>, map_KeyofT, 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);
		}


	private:
		YCH_CLOSE_HASH::HashTable<K, pair<K, V>, map_KeyofT, hash> _ht;

	};
};

set封装:

#pragma once
#include "hash.hpp"


namespace YCH_MAP
{
	
	template<class K, class hash = Hash<K>>
	class ych_unordered_set
	{

	private:
		struct set_KeyofT
		{
			const K& operator()(const K& k)
			{
				return k;
			}
		};

	public:
		typedef typename YCH_CLOSE_HASH::HashTable<K, K, set_KeyofT, hash>::Iterator iterator;

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

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

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

		iterator find(const K &k)
		{
			return _ht.Find(k);
		}


	private:
		YCH_CLOSE_HASH::HashTable<K, K, set_KeyofT, hash> _ht;

	};
};

实验检测:

#include "hash.hpp"
#include "hashmap.hpp"
#include "hashset.hpp"


void test_map()
{

	YCH_MAP::ych_unordered_map<int, int> map;



	map.insert(make_pair(1, 1));
	map.insert(make_pair(2, 2));
	map.insert(make_pair(3, 3));
	map.insert(make_pair(4, 4));

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

	cout << "_______测试2_______" << endl;

	YCH_MAP::ych_unordered_map<string, string> map2;
	map2.insert(make_pair("苹果", "好吃"));
	map2.insert(make_pair("香蕉", "bu好吃"));
	map2.insert(make_pair("哈密瓜", "还可以"));
	map2.insert(make_pair("凤梨", "也还可以"));
	map2.insert(make_pair("水蜜桃", "也还不错"));

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

void test_set()
{
	cout << "-------set封装测试----" << endl;
	YCH_MAP::ych_unordered_set<int> set;

	set.insert(1);
	set.insert(12);
	set.insert(13);
	set.insert(10);
	set.insert(8);

	for (auto &e : set)
	{
		cout << e << endl;
	}

	cout << "_____查找10,并且输出____" << endl;
	YCH_MAP::ych_unordered_set<int>::iterator it = set.find(10);
	if (it != set.end())
		cout << *it << endl;

}
int main()
{
	test_map();
	test_set();



	system("pause");
	return 0;
}

实验效果:

image-20210610182737872

4.开散列

4.1开散列概念

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

image-20210611200549093

4.2开散列扩容

桶的个数是一定的,不断的插入元素,会导致单个的桶的长度很长,影响哈希表的性能,理想的情况下是每个桶下面只有一个节点。哈希桶的载荷因子控制在1,当大于1的时候就进行扩容,这样平均下来,每个桶下面只有一个节点;

**与开散列进行比较:**看起来哈希桶之中存储节点的指针开销比较大,其实不然。开散列的载荷因子保证小于0.7,来确保有足够的空间降低哈希冲突的概率,而表项的空间消耗远远高于指针所占的空间效率,因此哈希桶更能节省空间

image-20210611205857742

4.3总结(开散列和闭散列的比较)

**1.开散列的载荷因子:**α<=1,即平均每个桶下面挂一个节点,平均时间复杂度为1

2.开散列产生哈希冲突时:直接头插至链表之中,而闭散列就会出现哈希冲突,容易出现踩踏效应(二次探测也只是缓解这种情况)

3.开散列的优缺点:

优点:
不同位置冲突时,不再互相干扰,载荷因子一般控制在1

缺点:
迭代器遍历输出的时候,不是有序输出的。

**延伸:**要想做到有序输出,那么必须再用个list保存一份,哈希表里面存个地址指向对应的list,输出的时候就输出list里面的内容,但是这样的空间和时间消耗的代价就更大了

**4.开散列的优化:**如果所有的数据都冲突到一个桶下面了,怎么办?

1.在桶下面挂红黑树:极限也是lgN的时间复杂度,但是也只是这一会而已,当增容的时候,这种现象就会缓解

2.多阶哈希:多个哈希表,冲突的时候,挂到另外一个哈希表上,长度不一样,对应的位置就不一样

image-20210611205935284

5.开散列增容那一下的性能

image-20210611205920197

4.4代码实现以及效果验证

#ifndef _HASH_HPP_
#define _HASH_HPP_

using namespace std;
#include <iostream>
#include <vector>
#include <assert.h>
#include <string> 

static size_t GetNextPrime(size_t prime)
{
	static 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, 429496729ul
	};

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

	return primeList[i];
}

namespace YCH_OPEN_HASH
{
	//节点
	template<class T>
	struct HashLink
	{
		HashLink<T> *_next;
		T _t;

		HashLink(const T& t)
			:_t(t)
			,_next(nullptr)
		{}
	};

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

	//迭代器
	template<class K, class T,class Ref,class Ptr ,class KeyofT, class Hash>
	struct HashIterator
	{
		typedef HashLink<T> Node;
		typedef HashTable<K, T, KeyofT, Hash> HashTable;
		typedef HashIterator<K, T, Ref, Ptr, KeyofT,Hash> Self;

		Node *_node;//节点
		HashTable *_pht;//哈希表指针,++的时候需要计算位置

		HashIterator(Node *node,HashTable* tables)//构造函数需要传入节点指针,和哈希表指针
			:_node(node)
			, _pht(tables)
		{}

		Ref operator*()
		{
			assert(_node);
			return _node->_t;
		}

		Ptr operator->()
		{
			assert(_node);
			return &(_node->_t);//返回去的是一个地址,使用的时候编译器优化,减少了一个箭头
		}

		KeyofT kf;
		Self &operator++()//前置
		{
			size_t pos = _pht->HashFunc(kf(_node->_t),_pht->_tables);//获取当前位置
			pos++;
			_node = _node->_next;
			if (_node == nullptr)//当前链表走完了,寻找下一个节点
			{
				for (int i = pos; i < _pht->_tables.size(); i++)
				{
					if (_pht->_tables[i] != nullptr)
					{
						_node = _pht->_tables[i];
						break;
					}
				}
			}
			return *this;
		}

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

	};



	template<class K, class T, class KeyofT,class Hash>
	class HashTable
	{
	public:
		typedef HashLink<T> Node;
		typedef HashIterator<K, T, T&, T*, KeyofT, Hash> Iterator;
		typedef HashIterator<K, T, const T&, const T*, KeyofT, Hash> Const_Iterator;
		
		//迭代器友元
		friend HashIterator<K, T, T&, T*, KeyofT, Hash>;
		friend Const_Iterator;


		Iterator Begin()
		{
			for (int i = 0; i < _tables.size(); i++)
			{
				if (_tables[i] != nullptr)
					return Iterator(_tables[i], this);
			}
			return Iterator(nullptr, this);
		}

		Iterator End()
		{
			return Iterator(nullptr, this);
		}


		KeyofT kf;//提取key值

		//哈希函数
		size_t HashFunc(const K& key,const vector<HashLink<T>*> tables)
		{
			Hash hf;
			return hf(key) % tables.size();
		}

		pair<Iterator,bool> Insert(const T& t)
		{
			//判断是否存在
			if (_tables.size())//防止%0
			{
				Iterator fi = Find(kf(t));
				if (fi != End())//存在
					return make_pair(fi, false);
			}

			//判断是否需要扩容
			if (_size == _tables.size())//α<=1
			{
				size_t newsize = GetNextPrime(_tables.size());
				vector<Node*> newtables(newsize, nullptr);//构造一个新表出来

				for (int i = 0; i < _tables.size(); i++)
				{
					Node* node = _tables[i];
					while (node)//当前位置有节点
					{
						Node *next = node->_next;//保存当前链表的下一个位置

						size_t index = HashFunc(kf(node->_t),newtables);//得到位置
						node->_next = newtables[index];
						newtables[index] = node;

						node = next;
					}
					_tables[i] = nullptr;//原表置空
				}

				//两表交换
				newtables.swap(_tables);
			}

			//插入节点

			size_t index = HashFunc(kf(t),_tables);//寻找插入位置
			Node *newnode = new Node(t);//构造一个节点
			newnode->_next = _tables[index];//头插
			_tables[index] = newnode;
			_size++;

			return make_pair(Iterator(_tables[index], this), true);
		}

		//查找
		Iterator Find(const K& k)
		{
			size_t index=HashFunc(k, _tables);

			Node *cur = _tables[index];
			while (cur&&(kf(cur->_t) !=k))
			{
				cur = cur->_next;
			}
			return Iterator(cur, this);
		}

		删除
		bool Erase(const K& k)
		{
			size_t intdex = HashFunc(k, _tables);
			Node* cur = _tables[index];
			Node *prev = nullptr;
			while (cur&&kf(cur->_t) != k)
			{
				prev = cur;
				cur = cur->_next;
			}
			if (cur == nullptr)//没找到
				return false;

			prev->_next = cur->_next;
			delete cur;
			_size--;
			return true;
		}

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

#endif

#pragma once
#include "hash.hpp"


namespace YCH_MAP
{
	//内置哈希转换函数 <-> 常用int 和 string 
	//如果K类型不支持取模,就需要配上一个仿函数来进行使用
	template<class K>
	struct Hash
	{
		size_t operator() (const K&key)
		{
			return key;
		}
	};

	//string类型常用,进行特化
	template<>
	struct Hash<string>
	{
		size_t operator() (const string &key)
		{
			size_t count = 0;
			for (auto&e : key)
			{
				count = count * 131 + e;// 字符串转整形求哈希地址常用值131,可以减少冲突
			}
			return count;
		}
	};

	template<class K, class V, class hash = Hash<K>>
	class ych_unordered_map
	{

	private:
		struct map_KeyofT
		{
			const K& operator()(const pair<const K, V>& kv)
			{
				return kv.first;
			}
		};

	public:
		typedef typename YCH_OPEN_HASH::HashTable<K, pair<const K, V>, map_KeyofT, hash>::Iterator iterator;

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

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

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

		V& operator[](const K& k)
		{
			return ((insert(make_pair(k, V()))).first)->second;
		}


	private:
		YCH_OPEN_HASH::HashTable<K, pair<const K, V>, map_KeyofT, hash> _ht;

	};
};
#pragma once
#include "hash.hpp"


namespace YCH_MAP
{
	
	template<class K, class hash = Hash<K>>
	class ych_unordered_set
	{

	private:
		struct set_KeyofT
		{
			const K& operator()(const K& k)
			{
				return k;
			}
		};

	public:
		//typedef typename YCH_CLOSE_HASH::HashTable<K, K, set_KeyofT, hash>::Iterator iterator;闭散列
		typedef typename YCH_OPEN_HASH::HashTable<K, K, set_KeyofT, hash>::Iterator iterator;//开散列


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

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

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

		iterator find(const K &k)
		{
			return _ht.Find(k);
		}


	private:
		//YCH_CLOSE_HASH::HashTable<K, K, set_KeyofT, hash> _ht;
		YCH_OPEN_HASH::HashTable<K, K, set_KeyofT, hash> _ht;


	};
};


#include "hash.hpp"
#include "hashmap.hpp"
#include "hashset.hpp"

void test_open_map()
{
	cout << "____YCH_MAP::ych_unordered_map<int, int> map___" << endl ;
	YCH_MAP::ych_unordered_map<int, int> map;
	
	map.insert(make_pair(1, 1));
	map.insert(make_pair(54, 54));
	map.insert(make_pair(55, 55));
	map.insert(make_pair(56, 56));
	map.insert(make_pair(54, 54));
	map.insert(make_pair(108, 108));

	auto it = map.begin();

	while (it != map.end())
	{
		cout << it->first << " " << it->second << endl;
		++it;
	}
	cout << endl;


	cout << "____[]测试____<<endl";
	map[500]++;
	map[200]=100;
	map[111] = 153;

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

void test_open_set()
{
	cout << "_____YCH_MAP::ych_unordered_set<string> set______ " << endl;
	YCH_MAP::ych_unordered_set<string> set;
	set.insert("排序");
	set.insert("字符串");
	set.insert("算法");
	set.insert("算法");
	set.insert("字符串");
	set.insert("哈希表");

	for (auto&e : set)
	{
		cout << e << endl;
	}
}

int main()
{
	test_open_map();	
	test_open_set();



	system("pause");
	return 0;

}

在这里插入图片描述

以上是关于哈希表底层探索的主要内容,如果未能解决你的问题,请参考以下文章

leetcode 数据结构 探索哈希表

在底层,Javascript 对象是哈希表吗? [复制]

48 容器——HashMap底层:哈希表结构与哈希算法

JavaScript笔试题(js高级代码片段)

简单了解unordered_set和unordered_map底层

Redis技术探索「底层架构原理」探索分析服务核心数据结构介绍和案例