C++笔记归纳17:哈希

哈希

目录

哈希

[一、unordered_map & unordered_set](#一、unordered_map & unordered_set)

二、哈希表的实现

2.1.哈希的概念

2.2.直接定址法

2.3.哈希冲突(哈希碰撞)

2.4.负载因子(载荷因子/装载因子)

2.5.将关键字转为整数

2.6.哈希函数

2.6.1.除法散列法(除留余数法)

2.6.2.乘法散列法

2.6.3.全域散列法

2.7.哈希冲突的处理

2.7.1.开放定址法

2.7.1.1.线性探测

2.7.1.2.二次探测

2.7.1.3.双重散列

2.7.2.链地址法

三、哈希封装

3.1.源码及框架分析

3.2.实现哈希表

3.3.封装框架

3.4.实现迭代器

3.5.链地址法总代码


一、unordered_map & unordered_set

unorderer_set的声明

cpp 复制代码
template < class Key, //
unordered_set::key_type/value_type
class Hash = hash<Key>, // unordered_set::hasher
class Pred = equal_to<Key>, // unordered_set::key_equal
class Alloc = allocator<Key> // unordered_set::allocator_type
> class unordered_set;

unordered_set和set的差异

使用差异

  • set要求Key支持小于比较
  • unordered_set要求Key支持转成整型,并且支持等于比较

迭代器差异

  • set是双向迭代器
  • unordered_set是单向迭代器
  • set底层是红黑树,迭代器遍历:有序+去重
  • unordered_set底层是哈希表,迭代器遍历:无序+去重

性能差异

  • 红黑树增删查改:O(logN)
  • 哈希表增删查改:O(1)

性能测试

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <set>
#include <unordered_set>

int test_set2()
{
	const size_t N = 100000000;
	unordered_set<int> us;
	set<int> s;
	vector<int> v;
	v.reserve(N);
	srand(time(0));
	for (size_t i = 0; i < N; ++i)
	{
		v.push_back(rand()); //N比较大时,重复值比较多
		//v.push_back(rand() + i); //重复值相对少
		//v.push_back(i); //没有重复,有序
	}
	// 21:15
	size_t begin1 = clock();
	for (auto e : v)
	{
		s.insert(e);
	}
	size_t end1 = clock();
	cout << "set insert:" << end1 - begin1 << endl;
	size_t begin2 = clock();
	us.reserve(N);
	for (auto e : v)
	{
		us.insert(e);
	}
	size_t end2 = clock();
	cout << "unordered_set insert:" << end2 - begin2 << endl;
	
	int m1 = 0;
	size_t begin3 = clock();
	for (auto e : v)
	{
		auto ret = s.find(e);
		if (ret != s.end())
		{
			++m1;
		}
	}
	size_t end3 = clock();
	cout << "set find:" << end3 - begin3 << "->" << m1 << endl;
	
	int m2 = 0;
	size_t begin4 = clock();
	for (auto e : v)
	{
		auto ret = us.find(e);
		if (ret != us.end())
		{
			++m2;
		}
	}
	size_t end4 = clock();
	cout << "unorered_set find:" << end4 - begin4 << "->" << m2 << endl;
	
	cout << "插入数据个数:" << s.size() << endl;
	cout << "插入数据个数:" << us.size() << endl << endl;
	
	size_t begin5 = clock();
	for (auto e : v)
	{
		s.erase(e);
	}
	size_t end5 = clock();
	cout << "set erase:" << end5 - begin5 << endl;

	size_t begin6 = clock();
	for (auto e : v)
	{
		us.erase(e);
	}
	size_t end6 = clock();
	cout << "unordered_set erase:" << end6 - begin6 << endl << endl;
	return 0;
}

int main()
{
	test_set2();
	return 0;
}

unordered_map和map的差异

使用差异

  • map要求Key支持小于比较
  • unordered_map要求Key支持转成整型,并且支持等于比较

迭代器差异

  • map是双向迭代器
  • nordered_map是单向迭代器
  • map底层是红黑树,迭代器遍历:Key有序+去重
  • unordered_map底层是哈希表,迭代器遍历:Key无序+去重

性能差异

  • 红黑树增删查改:O(logN)
  • 哈希表增删查改:O(1)

unordered_multimap/unordered_multiset

与multimap/multiset的功能完全类似,但支持Key冗余

差异也为Key的要求差异iterator及其遍历顺序的差异性能的差异

二、哈希表的实现

2.1.哈希的概念

**哈希(hash):**散列(散乱排列),一种组织数据的方式

本质: 通过哈希函数 把关键字Key存储位置 建立一个映射关系

2.2.直接定址法

当关键字的范围比较集中,并且为整型时,直接定址法就是简单高效的方法

**试题:**字符串中的第一个唯一字符

题目内容:

给定一个字符串s ,找到它的第一个不重复的字符,并返回它的索引

如果不存在,则返回-1

示例:

输入: s = "leetcode"

输出: 0

cpp 复制代码
class Solution 
{
public:
    int firstUniqChar(string s) 
    {
        int count[26] = {0};

        //统计次数
        for(auto ch : s)
        {
            count[ch - 'a']++;
        }

        for(size_t i = 0;i < s.size();++i)
        {
            if(count[s[i] - 'a'] == 1)
            {
                return i;
            }
        }

        return -1;
    }
};

2.3.哈希冲突(哈希碰撞)

**哈希冲突:**两个不同的key可能会映射到同一个位置

示例:3 % 200 == 3 203 % 200 == 3

**解决方案:**设计出优秀的哈希函数,减少冲突次数

2.4.负载因子(载荷因子/装载因子)

假设哈希表中已经映射存储了N个值,哈希表的大小为M

**负载因子(load factor):**N / M

  • 负载因子越大,哈希冲突概率越高,空间利用率越高
  • 负载因子越小,哈希冲突概率越低,空间利用率越低

2.5.将关键字转为整数

关键字映射到数组的位置,一般是整数好实现映射计算

如果关键字不为整数,那么就需要将关键字转换成整数

2.6.哈希函数

当关键字的范围比较分散时,会浪费内存,甚至导致内存不够用

假设数据范围为[0,9999]的N个值,映射到一个M个空间的数组

就需要借助哈希函数(hash function)

关键字key放到数组的h(key)位置,h计算出的值在[0,M)之间

(注:一般情况下M >= N)

**设计方向:**尽可能让N个关键字被等概率的均匀的散列分布到哈希表的M个空间中

2.6.1.除法散列法(除留余数法)

**哈希函数:**h(key) = key % M

假设哈希表的大小为M,通过key除以M的余数作为映射位置的下标

(注:需要避免M为2的幂、10的幂)

示例1:{63,31}

M取16(2^4),计算的哈希值都为15

63的二进制后八位:0011 1111

31的二进制后八位:0001 1111

所以都取的是:1111(15)

(注:十进制数模上2的幂次方数,取的是十进制数的二进制后的幂次方数位)

示例2:{112,12312}

M取100(10^2),计算的哈希值都为12

112的十进制后两位:12

12312的十进制后两位:12

所以都取的是:12(12)

建议M取不太接近2的整数次幂的一个质数(素数)

(注:质数是大于1的自然数,并且只能被1和自己整除)

java的HashMap采用除法散列法时用的2的整数次幂做哈希表的大小M

但是它通过进行位运算,取模得到n位后的值,再与n位前几位进行异或

int hash = key &(1 << n - 1)

hash = hash ^ (key >> (32 - n))

cpp 复制代码
#pragma once
#include <iostream>
using namespace std;
#include <vector>
#include "math.h"

enum State
{
	EXIST,
	EMPTY,
	DELETE
};

template<class K,class V>
struct HashData
{
	pair<K, V> _kv;
	State _state = EMPTY;
};

template<class K,class V>
class HashTable
{
public:
	HashTable()
		:_tables(pow(2,_m))
		, _n(0)
	{}

	size_t HashFunc(const K& key)
	{
		size_t hash = key & (_tables.size() - 1);
		hash ^= (key >> (32 - _m));
		return hash;
	}

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

		if (_n * 10 / _tables.size() >= 7)
		{
			HashTable<K, V> newht;
			++_m;
			newht._tables.resize(pow(2, _m));
			for (auto& data : _tables)
			{
				if (data._state == EXIST)
				{
					newht.Insert(data._kv);
				}
			}
			_tables.swap(newht._tables);
		}

		size_t hash0 = HashFunc(kv.first);
		size_t hashi = hash0;
		size_t i = 1;
		while (_tables[hashi]._state == EXIST)
		{
			hashi = (hash0 + i) % _tables.size();
			++i;
		}
		_tables[hashi]._kv = kv;
		_tables[hashi]._state = EXIST;
		++_n;
		return true;
	}

	HashData<K, V>* Find(const K& key)
	{
		size_t hash0 = HashFunc(key);
		size_t hashi = hash0;
		size_t i = 1;
		while (_tables[hashi]._state != EMPTY)
		{
			if (_tables[hashi]._state == EXIST && _tables[hashi]._kv.first == key)
			{
				return &_tables[hashi];
			}
			hashi = (hash0 + i) % _tables.size();
			++i;
		}
		return nullptr;
	}

	bool Erase(const K& key)
	{
		HashData<K, V>* ret = Find(key);
		if (ret)
		{
			ret->_state = DELETE;
			--_n;
			return true;
		}
		else
		{
			return false;
		}
	}
private:
	vector<HashData<K, V>> _tables;
	size_t _n;
	size_t _m = 16;
};
2.6.2.乘法散列法

对哈希表的大小M没有要求,用关键字key乘一个常数A(0 < A < 1)

抽取出k * A 的小数部分,再用M乘k * A 的小数部分,向下取整

**哈希函数:**h(key) = floor (M * ((A * key) % 1.0 )

floor():对表达式进行向下取整

A:A∈(0,1),Knuth建议取黄金分割点(0.6180339887...)

**示例:**假设M为1024,key为1234,A取0.6180339887

A * key = 762.6539420558

取小数部分为0.6539420558

M * 0.6539420558 = 669.6366651392

floor(669.6366651392) = 669

由此可得:h(1234) = 669

2.6.3.全域散列法

针对散列函数,特意构造出一个发生严重冲突的数据集

哈希表的效率急剧降低,导致访问速度变慢

只要散列函数公开,就可以实现此攻击

为了防止这种攻击,可以使用全域散列

**哈希函数:**hab(key) = ((a * key + b) % P)% M

P:足够大的质数

a:随机选取[1,P-1]之间的任意整数

b:随机选取[0,P-1]之间的任意整数

这些函数构成了一个P * (P - 1)组的全域散列函数组

示例:假设P = 17,M = 6,a = 3,b = 4

h34(8) = ((3 * 8 + 4) % 17)% 6 = 5

分布式与集群

一台机器无法存储处理海量的数据,必须要采取分布式

将数据存储在多台机器(集群),进行存储与运算处理

**示例:**给一张照片生成一个ID(字符串),如何知晓照片存放在哪一台机器?

将照片ID转换为整型,然后模1024,得到存储的机器编号

2.7.哈希冲突的处理

2.7.1.开放定址法

所有元素都放到哈希表里,当关键字key用哈希函数计算出的位置发生冲突

照某种规则找到一个没有存储数据的位置进行存储,负载因子一定要小于1

2.7.1.1.线性探测

从发生冲突的位置开始,依次线性向后探测,直到寻找到下一个没有存储数据的位置为止

如果走到哈希表尾,则回绕到哈希表头的位置

h(key) = key % M = hash0,如果hash0位置冲突

**线性探测公式:**hc(key,i) = (hash0 + i) % M,i = {1,2,3,...,M - 1} = hashi

因为负载因子小于1,则最多探测M - 1次,一定能够找到一个存储key的位置

**线性探测问题:**群集(堆积)现象

hash0,hash1,hash2位置已经存储数据了

后续映射到hash0,hash1,hash2,hash3的值都会争夺hash3的位置

将{19,30,5,36,13,20,21,12}这组值映射到M = 11的表中

h(19) = 8 h(30) = 8 h(5) = 5 h(36) = 3 h(13) = 2 h(20) = 9 h(21) = 10 h(12) = 1

每个存储值的位置加一个状态标识,否则删除一些数据后,会影响后面冲突值的查找

比如:如果删除30,下标9的位置就会没有数据,不会进行探测,导致20查找失败

给每个位置加一个标识{EXIST,EMPTY,DELETE},这样删除30可以不用删除值

而是把状态改为DELETE,查找20时遇到EMPTY

**2.7.1.2.**二次探测

从发生冲突的位置开始,依次左右按二次方跳跃式探测,直到寻找到下一个没有存储数据的位置

如果往右走到哈希表尾,则回绕到哈希表头的位置

如果往左走到哈希表头,则回绕到哈希表尾的位置

h(key) = key % M = hash0,如果hash0位置冲突

**二次探测公式:**hc(key,i) = (hash0 ± i^2) % M,i = {1,2,3,...,M/2} = hashi

先加后减:+1,-1,+2,-2,+9,-9,+16,-16......

当hashi = (hash0 - i^2) % M,如果hashi < 0,需要hashi += M

cpp 复制代码
#pragma once
#include <iostream>
using namespace std;
#include <vector>

enum State
{
	EXIST,
	EMPTY,
	DELETE
};

template<class K,class V>
struct HashData
{
	pair<K, V> _kv;
	State _state = EMPTY;
};

template<class K,class V>
class HashTable
{
public:
	HashTable()
		:_tables(11)
		,_n(0)
	{}

	bool Insert(const pair<K, V>& kv)
	{
		//保证没有数据冗余
		if (Find(kv.first))
		{
			return false;
		}

		//写法1:
		//负载因子大于等于0.7
		//if (_n * 10 / _table.size() >= 7)
		//{
		//	//扩容
		//	vector<HashData<K, V>> newtables(_table.size() * 2);
		//	//更新映射关系
		//	for (auto& data : _tables)
		//	{
		//		if (data._state == EXIST)
		//		{
		//			size_t hash0 = data._kv.first % newtables.size();
		//			//...
		//		}
		//	}
		//	_tables.swap(newtables);
		//}

		//写法2:
		//负载因子大于等于0.7
		if (_n * 10 / _tables.size() >= 7)
		{
			HashTable<K, V> newht;
			newht._tables.resize(_tables.size() * 2);
			for (auto& data : _tables)
			{
				if (data._state == EXIST)
				{
					newht.Insert(data._kv);
				}
			}
			_tables.swap(newht._tables);
		}

		//计算映射值hash0
		//用key值模上哈希表的size
		//(注:这里不能用capacity)
		size_t hash0 = kv.first % _tables.size();
		
		size_t hashi = hash0;
		size_t i = 1;
		//int flag = 1;
		//循环条件为当前值的状态为存在
		while (_tables[hashi]._state == EXIST)
		{
			//线性探测
			hashi = (hash0 + i) % _tables.size();
			//二次探测将i变为i * i * flag
			//if (hashi < _tables.size())
			//{
			//	hashi += _table.size();
			//}
			//if (flag == 1)
			//{
			//	flag = -1;
			//}
			//else
			//{
			//	++i;
			//	flag = 1;
			//}
			++i;
		}
		_tables[hashi]._kv = kv;
		_tables[hashi]._state = EXIST;
		++_n;
		return true;
	}

	HashData<K, V>* Find(const K& key)
	{
		size_t hash0 = key % _tables.size();
		size_t hashi = hash0;
		size_t i = 1;
		//循环条件为当前值的状态不为空
		while (_tables[hashi]._state != EMPTY)
		{
			//如果当前key值相等,且数据存在,查找成功
			if (_tables[hashi]._state == EXIST && _tables[hashi]._kv.first == key)
			{
				return &_tables[hashi];
			}
			//线性探测
			hashi = (hash0 + i) % _tables.size();
			++i;
		}
		return nullptr;
	}

	bool Erase(const K& key)
	{
		HashData<K, V>* ret = Find(key);
		if (ret)
		{
			ret->_state = DELETE;
            --_n;
			return true;
		}
		else
		{
			return false;
		}
	}
private:
	vector<HashData<K, V>> _tables;
	size_t _n;//数据个数
};

扩容问题

扩容后,为了保持哈希表的大小是一个质数(第一个是质数,但2倍扩容后就不为质数了)

SGI版本的哈希表给了一个近似2倍的质数表,每次从质数表中获取扩容后的大小

cpp 复制代码
inline unsigned long __stl_next_prime(unsigned long n)
{
	// Note: assumes long is at least 32 bits.
	static const int __stl_num_primes = 28;
	static const unsigned long __stl_prime_list[__stl_num_primes] =
	{
		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
	};
	
	const unsigned long* first = __stl_prime_list;
	const unsigned long* last = __stl_prime_list + __stl_num_primes;
	const unsigned long* pos = lower_bound(first, last, n);
	return pos == last ? *(last - 1) : *pos;
}

Key不能取模问题

当key为string或Date等类型时,key无法取模

需要给HashTable增加一个仿函数,把key转换成一个可以取模的整型

如果key可以转换为整型,且不容易冲突(比如:地址),仿函数就用默认参数即可

如果key无法转换为整型(比如:string、Date),需要实现一个仿函数,转换为整型

**示例1:**string做哈希表的key

cpp 复制代码
#pragma once
#include <iostream>
using namespace std;
#include <vector>

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

	template <class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		State _state = EMPTY;
	};

	template <class K>
	struct HashFunc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};

	template <>
	struct HashFunc <string>
	{
		//字符串转换成整形,可以把字符串的ASCII码相加
		//但是直接相加的话"abcd"和"bcad"的计算结果相同
		//BKDR哈希的思路,用上次的计算结果去乘一个质数
		//这个质数一般取31, 131等效果比较好
		size_t operator()(const string& key)
		{
			size_t hash = 0;
			for (auto e : key)
			{
				hash *= 131;
				hash += e;
			}

			return hash;
		}
	};

	template < class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
	public:
		HashTable()
		{
			_tables.resize(__stl_next_prime(0));
		}

		inline unsigned long __stl_next_prime(unsigned long n)
		{
			static const int __stl_num_primes = 28;
			static const unsigned long __stl_prime_list[__stl_num_primes] =
			{
				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
			};

			const unsigned long* first = __stl_prime_list;
			const unsigned long* last = __stl_prime_list + __stl_num_primes;
			const unsigned long* pos = lower_bound(first, last, n);
			return pos == last ? *(last - 1) : *pos;
		}

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

			if (_n * 10 / _tables.size() >= 7)
			{
				HashTable<K, V, Hash> newHT;
				newHT._tables.resize(__stl_next_prime(_tables.size() + 1));
				for (size_t i = 0; i < _tables.size(); i++)
				{
					if (_tables[i]._state == EXIST)
					{
						newHT.Insert(_tables[i]._kv);
					}
				}

				_tables.swap(newHT._tables);
			}

			Hash hash;
			size_t hash0 = hash(kv.first) % _tables.size();
			size_t hashi = hash0;
			size_t i = 1;
			while (_tables[hashi]._state == EXIST)
			{
				hashi = (hash0 + i) % _tables.size();
				++i;
			}

			_tables[hashi]._kv = kv;
			_tables[hashi]._state = EXIST;
			++_n;

			return true;
		}

		HashData<K, V>* Find(const K& key)
		{
			Hash hash;
			size_t hash0 = hash(key) % _tables.size();
			size_t hashi = hash0;
			size_t i = 1;
			while (_tables[hashi]._state != EMPTY)
			{
				if (_tables[hashi]._state == EXIST && _tables[hashi]._kv.first == key)
				{
					return &_tables[hashi];
				}

				hashi = (hash0 + i) % _tables.size();
				++i;
			}

			return nullptr;
		}

		bool Erase(const K& key)
		{
			HashData<K, V>* ret = Find(key);
			if (ret == nullptr)
			{
				return false;
			}
			else
			{
				ret->_state = DELETE;
				--_n;
				return true;
			}
		}
	private:
		vector<HashData<K, V>> _tables;
		size_t _n = 0;
	};
}

测试文件

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include "HashTable.h"

int main()
{
	const char* a1[] = { "abcd","bcad","aadd"};
	open_address::HashTable<string, string, open_address::HashFunc<string>> ht1;
	for (auto e : a1)
	{
		ht1.Insert({ e,e });
	}
	for (auto e : a1)
	{
		cout << e << endl;
	}
	cout << endl;
	cout << open_address::HashFunc<string>()("abcd") << endl;
	cout << open_address::HashFunc<string>()("bcad") << endl;
	cout << open_address::HashFunc<string>()("aadd") << endl;
	return 0;
}

**示例2:**Date做哈希表的key

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include "HashTable.h"

struct Date
{
	int _year;
	int _month;
	int _day;

	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{}

	bool operator==(const Date& d)
	{
		return _year == d._year && _month == d._month && _day == d._day;
	}
};

struct DateHashFunc
{
	size_t operator()(const Date& d)
	{
		size_t hash = 0;
		hash += d._year;
		hash *= 131;
		hash += d._month;
		hash *= 131;
		hash += d._day;
		hash *= 131;
		return hash;
	}
};

int main()
{
	open_address::HashTable<Date, int> ht;
	ht.Insert({ {2024,10,12},1 });
	ht.Insert({ {2026,3,28},1 });
	return 0;
}

注:

如果key值为自定义类型,必须要对等于进行运算符重载,string有自带的运算符重载,但Date没有

需要手写,等于的运算符重载在unordered_map中是用Pred仿函数进行包装

2.7.1.3.双重散列

第一个哈希函数计算出的值发生冲突

使用第二个哈希函数计算出一个与key相关的偏移量值

不断往后检测,直到寻找到下一个没有存储数据的位置为止

h1(key) = key % M = hash0,如果hash0位置冲突

**双重探测公式:**hc(key,i) = (hash0 + i * h2(key))% M,i = {1,2,3,...,M}

要求h2(key) < M 且 h2(key)与M互为质数

取值方法1:当M为2整数幂时,h2(key)从[0,M-1]任选一个奇数

取值方法2:当M为质数时,h2(key) = key % (M - 1) + 1

本质:跳跃探测,减少冲突堆积

将{19,30,52,74}这组值映射到M = 11的表中,则h2(key) = key % 10 + 1

(注:实际插入顺序为52,74,19,30,i从1开始,每插入一个数加1)

2.7.2.链地址法

**开放定址法的缺陷:**发生哈希冲突后,会占领别人的位置

链地址法的思路:

哈希表中存储一个指针

没有数据映射这个位置时,这个指针为空

有多个数据映射这个位置,把冲突数据链接成一个链表(或者红黑树)

挂在哈希表的映射位置下面,这个方法也叫拉链法或者哈希桶

将{19,30,5,36,13,20,21,12,24,96}这一组值映射到M = 11的表中

h(19) = 8 h(30) = 8 h(5) = 5 h(36) = 3 h(13) = 2

h(20) = 9 h(21) = 10 h(12) = 1 h(24) = 2 h(96) = 8

扩容问题

开放定址法的负载因子必须小于1

链地址法的负载因子可以大于1

  • 负载因子越大,哈希冲突的概率越高,空间利用率越高
  • 负载因子越小,哈希冲突的概率越低,空间利用率越低

STL中哈希的最大负载因子基本控制在1,大于1就扩容

当某个桶过长时,可以使用全域散列法,也可以将链表转为红黑树

cpp 复制代码
namespace hash_bucket
{
	template < class K, class V>
	struct HashNode
	{
		pair<K, V> _kv;
		HashNode<K, V>* _next;
		
		HashNode(const pair<K, V>&kv)
			:_kv(kv)
			,_next(nullptr)
		{}
	};
	
	template <class K>
	struct HashFunc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};

	template < class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;
		
		inline unsigned long __stl_next_prime(unsigned long n)
		{
			static const int __stl_num_primes = 28;
			static const unsigned long __stl_prime_list[__stl_num_primes] =
			{
				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
			};
			
			const unsigned long* first = __stl_prime_list;
			const unsigned long* last = __stl_prime_list + __stl_num_primes;
			const unsigned long* pos = lower_bound(first, last, n);
			return pos == last ? *(last - 1) : *pos;
		}
		public:
			HashTable()
			{
				_tables.resize(__stl_next_prime(0), nullptr);
			}
							
			~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)
			{
				if (Find(kv.first))
				{
					return false;
				}

				Hash hs;
				size_t hashi = hs(kv.first) % _tables.size();
				
				if (_n == _tables.size())
				{
					vector<Node*>newtables(__stl_next_prime(_tables.size() + 1), nullptr);
					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();
							cur->_next = newtables[hashi];
							newtables[hashi] = cur;
							cur = next;
						}
					
						_tables[i] = nullptr;
					}
					_tables.swap(newtables);
				}
				Node * newnode = new Node(kv);
				newnode->_next = _tables[hashi];
				_tables[hashi] = newnode;
				++_n;
				
				return true;
			}
	
			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;
			}
			
			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)
					{
						//如果是头节点
						if (prev == nullptr)
						{ 
							_tables[hashi] = cur->_next;
						}
						else//如果不是头节点
						{
							prev->_next = cur->_next;
						}
						delete cur;
						--_n;
						return true;
					}
					prev = cur;
					cur = cur->_next;
				}
				return false;
			}
			
	private:
			vector<Node*> _tables; //指针数组
			size_t _n = 0;		   //表中存储数据个数
	};
}

三、哈希封装

3.1.源码及框架分析

SGI-STL30版本的哈希表是C++11之前的STL版本

unordered_map和unordered_set是C++11后更新的

它的容器名字叫hash_map和hash_set,为非标准容器

hash_map和hash_set的实现结构框架核心

cpp 复制代码
// stl_hash_set
template < class Value, class HashFcn = hash<Value>,
class EqualKey = equal_to<Value>,class Alloc = alloc>
class hash_set
{
	private:
	typedef hashtable<Value, Value, HashFcn, identity<Value>,
	EqualKey, Alloc> ht;
	ht rep;
	public:
	typedef typename ht::key_type key_type;
	typedef typename ht::value_type value_type;
	typedef typename ht::hasher hasher;
	typedef typename ht::key_equal key_equal;
		
	typedef typename ht::const_iterator iterator;
	typedef typename ht::const_iterator const_iterator;
		
	hasher hash_funct() const { return rep.hash_funct(); }
	key_equal key_eq() const { return rep.key_eq(); }
};

// stl_hash_map
template < class Key, class T, class HashFcn = hash<Key>,
class EqualKey = equal_to<Key>,class Alloc = alloc>
class hash_map
{
private:
	typedef hashtable<pair<const Key, T>, Key, HashFcn,
	select1st<pair<const Key, T> >, EqualKey, Alloc> ht;
	ht rep;
		
public:
	typedef typename ht::key_type key_type;
	typedef T data_type;
	typedef T mapped_type;
	typedef typename ht::value_type value_type;
	typedef typename ht::hasher hasher;
	typedef typename ht::key_equal key_equal;
		
	typedef typename ht::iterator iterator;
	typedef typename ht::const_iterator const_iterator;
};

// stl_hashtable.h
template < class Value, class Key, class HashFcn,class ExtractKey, class EqualKey,class Alloc>
class hashtable {
public:
		typedef Key key_type;
		typedef Value value_type;
		typedef HashFcn hasher;
		typedef EqualKey key_equal;
private:
		hasher hash;
		key_equal equals;
		ExtractKey get_key;
		typedef __hashtable_node<Value> node;
		
		vector<node*, Alloc> buckets;
		size_type num_elements;
public:
		typedef __hashtable_iterator<Value, Key, HashFcn, ExtractKey, EqualKey,Alloc> iterator;
		pair<iterator, bool> insert_unique(const value_type& obj);
		const_iterator find(const key_type & key) const;
};

template <class Value>
struct __hashtable_node
{
	__hashtable_node * next;
	Value val;
};

3.2.实现哈希表

HashTable.h

cpp 复制代码
#include <iostream>
using namespace std;
#include <vector>

template <class K>
struct HashFunc
{
	size_t operator()(const K & key)
	{
		return (size_t)key;
	}
};

namespace hash_bucket
{
	template <class T>
	struct HashNode
	{
		T _data;
		HashNode<T>*_next;
		
		HashNode(const T & data)
			:_data(data)
			,_next(nullptr)
		{}
	};

	template <class K, class T, class KeyOfT, class Hash>
	class HashTable
	{
		typedef HashNode<T> Node;
		
		inline unsigned long __stl_next_prime(unsigned long n)
		{
			
			static const int __stl_num_primes = 28;
			static const unsigned long __stl_prime_list[__stl_num_primes] =
			{
				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
			};
		
			const unsigned long* first = __stl_prime_list;
			const unsigned long* last = __stl_prime_list + __stl_num_primes;
			const unsigned long* pos = lower_bound(first, last, n);
			return pos == last ? *(last - 1) : *pos;
		}
		public:
			HashTable()
			{
				_tables.resize(__stl_next_prime(_tables.size()), nullptr);
			}
			
			~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 T & data)
			{
				KeyOfT kot;
				if (Find(kot(data)))
				{
					return false;
				}
				Hash hs;
				size_t hashi = hs(kot(data)) % _tables.size();
	
				if (_n == _tables.size())
				{
					vector<Node*> newtables(__stl_next_prime(_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);
				}
				
				Node * newnode = new Node(data);
				newnode->_next = _tables[hashi];
				_tables[hashi] = newnode;
				++_n;
				
				return true;
			}
	private:
			vector<Node*> _tables; // 指针数组
			size_t _n = 0; // 表中存储数据个数
	};
}

3.3.封装框架

UnorderedSet.h

cpp 复制代码
#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:
		bool insert(const K& key)
		{
			return _ht.Insert(key);
		}
	private:
		hash_bucket::HashTable<K, K, SetKeyOfT> _ht;
	};
}

UnorderedMap.h

cpp 复制代码
#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:
			bool insert(const pair<K, V>&kv)
			{
				return _ht.Insert(kv);
			}
		private:
			hash_bucket::HashTable<K, pair<K, V>, MapKeyOfT> _ht;
	};
}

3.4.实现迭代器

iterator核心源码

cpp 复制代码
template < class Value, class Key, class HashFcn, class ExtractKey, class EqualKey, class Alloc>
struct __hashtable_iterator 
{
	typedef hashtable < Value, Key, HashFcn, ExtractKey, EqualKey, Alloc> hashtable;
	typedef __hashtable_iterator < Value, Key, HashFcn,ExtractKey, EqualKey, Alloc> iterator;
	typedef __hashtable_const_iterator < Value, Key, HashFcn, ExtractKey, EqualKey, Alloc> const_iterator;
	typedef __hashtable_node<Value> node;
	
	typedef forward_iterator_tag iterator_category;
	typedef Value value_type;
	
	node * cur;
	hashtable * ht;
	
	__hashtable_iterator(node * n, hashtable * tab) : cur(n), ht(tab) {}
	__hashtable_iterator() {}
	reference operator*() const { return cur->val; }
#ifndef __SGI_STL_NO_ARROW_OPERATOR
	pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */
	iterator & operator++();
	iterator operator++(int);
	bool operator==(const iterator& it) const { return cur == it.cur; }
	bool operator!=(const iterator& it) const { return cur != it.cur; }
};

template <class V, class K, class HF, class ExK, class EqK, class A>
__hashtable_iterator<V, K, HF, ExK, EqK, A>&
__hashtable_iterator<V, K, HF, ExK, EqK, A>::operator++()
{
	const node* old = cur;
	cur = cur->next;
	if (!cur) 
    {
		size_type bucket = ht->bkt_num(old->val);
		while (!cur && ++bucket < ht->buckets.size())
        {
			cur = ht->buckets[bucket];
        }
	}
	return *this;
}

3.5.链地址法总代码

HashTable.h

cpp 复制代码
#pragma once
#include <iostream>
using namespace std;
#include <vector>

//key转整型仿函数
template <class K>
struct HashFunc
{
	size_t operator()(const K & key)
	{
		return (size_t)key;
	}
};

//key为string类型转整型的特化
template <>
struct HashFunc <string>
{
	size_t operator()(const string & key)
	{
		size_t hash = 0;
		for (auto e : key)
		{
			hash *= 131;
			hash += e;
		}
		return hash;
	}
};

namespace hash_bucket
{
	//哈希节点
	template <class T>
	struct HashNode
	{
		T _data;//节点值 set:key map:key + value
		HashNode<T>* _next;//下一个节点地址

		//节点构造
		HashNode(const T & data)
			:_data(data)
			,_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 HTIterator
	{
		//哈希节点重命名
		typedef HashNode<T> Node;
		//哈希迭代器重命名
		typedef HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;

		//创建哈希节点
		Node* _node;
		//创建哈希迭代器
		const HashTable<K, T, KeyOfT, Hash>* _pht;

		//迭代器构造
		HTIterator(Node* node, const HashTable<K, T, KeyOfT, Hash>* pht)
			:_node(node)
			,_pht(pht)
		{}

		//解引用重载
		Ref operator*()
		{
			return _node->_data;
		}

		//引用重载
		Ptr operator->()
		{
			return &_node->_data;
		}

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

		//前置++重载
		Self& operator++()
		{
			//如果下一个节点不为空
			if (_node->_next)
			{
				//将节点更新为下一个节点
				_node = _node->_next;
			}
			else//如果下一个节点为空
			{
				KeyOfT kot;
				Hash hash;
				//计算映射值
				size_t hashi = hash(kot(_node->_data)) % _pht->_tables.size();
				//去下一个桶查找
				++hashi;
				//循环条件为走到最后一个桶之后
				while (hashi < _pht->_tables.size())
				{
					//存储当前桶的头节点
					_node = _pht->_tables[hashi];
					//如果这个桶的头节点不为空
					if (_node)
					{
						//查找成功,退出循环
						break;
					}
					//继续查找
					++hashi;
				}

				//如果所有桶都遍历结束
				if (hashi == _pht->_tables.size())
				{
					//将哈希节点设为空
					_node = nullptr;
				}
				else//如果当前桶有节点
				{
					//将这个节点赋给哈希节点
					_node = _pht->_tables[hashi];
				}
			}
			return *this;
		}
	};

	template <class K, class T, class KeyOfT, class Hash>
	class HashTable
	{
		//哈希迭代器模板的友元声明
		template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
		friend struct HTIterator;
		//哈希节点的重命名
		typedef HashNode<T> Node;
	public:
		//哈希迭代器的重命名
		typedef HTIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;
		typedef HTIterator<K, T, const T&,const T*, KeyOfT, Hash> ConstIterator;
		
		Iterator Begin()
		{
			//如果数据个数为0
			if (_n == 0)
			{
				return End();
			}
			//遍历哈希表
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node * cur = _tables[i];
				//查找到第一个有效数据
				if (cur)
				{
					//返回第一个节点
					return Iterator(cur, this);
				}
			}
			return End();
		}

		Iterator End()
		{
			return Iterator(nullptr, this);
		}
		
		ConstIterator Begin() const
		{
			if (_n == 0)
			{
				return End();
			}
			
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node * cur = _tables[i];
				if (cur)
			    {
					return ConstIterator(cur, this);
				}
			}
			return End();
		}
		
		ConstIterator End() const
		{
			return ConstIterator(nullptr, this);
		}
	
		//质数表
		inline unsigned long __stl_next_prime(unsigned long n)
		{

			static const int __stl_num_primes = 28;
			static const unsigned long __stl_prime_list[__stl_num_primes] =
			{
				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
			};

			const unsigned long* first = __stl_prime_list;
			const unsigned long* last = __stl_prime_list + __stl_num_primes;
			const unsigned long* pos = lower_bound(first, last, n);
			return pos == last ? *(last - 1) : *pos;
		}

		//构造函数
		HashTable()
		{
			_tables.resize(__stl_next_prime(_tables.size()), nullptr);
		}

		//析构函数
		~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;
			}
		}
		
		//插入
		pair<Iterator, bool> Insert(const T& data)
		{
			KeyOfT kot;
			Iterator it = Find(kot(data));
			//如果当前值已经存在
			if (it != End())
			{
				//插入失败
				return make_pair(it, false);
			}
			Hash hs;
			//计算映射值
			size_t hashi = hs(kot(data)) % _tables.size();
			//如果负载因子为1,哈希表填满,需要扩容
			if (_n == _tables.size())
			{
				//创建新的哈希表
				vector<Node*> newtables(__stl_next_prime(_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);
			}
			//头插
			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 End();
		}
		
		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 == nullptr)
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					--_n;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}
	private:
			vector<Node*> _tables; //指针数组
			size_t _n = 0;		   //表中存储数据个数
	};
}

UnorderedMap.h

cpp 复制代码
#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;
		typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::ConstIterator const_iterator;
		iterator begin()
		{
			return _ht.Begin();
		}
			
		iterator end()
		{
			return _ht.End();
		}
			
		const_iterator begin() const
		{
			return _ht.Begin();
		}
			
		const_iterator end() const
		{
			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;
		}
			
		iterator Find(const K & key)
		{
			return _ht.Find(key);
		}
		
		bool Erase(const K & key)
		{
			return _ht.Erase(key);
		}
	private:
		hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
	};
}

UnorderedSet.h

cpp 复制代码
#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;
		typedef typename hash_bucket::HashTable<K,const K, SetKeyOfT, Hash>::ConstIterator const_iterator;
		iterator begin()
		{
			return _ht.Begin();
		}

		iterator end()
		{
			return _ht.End();
		}
		
		const_iterator begin() const
		{
			return _ht.Begin();
		}
		
		const_iterator end() const
		{
			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.Erase(key);
		}
	private:
		hash_bucket::HashTable<K,const K, SetKeyOfT, Hash> _ht;
	};
}

测试代码

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include "HashTable.h"
#include "UnorderedSet.h"
#include "UnorderedMap.h"

void test_map()
{
	bit::unordered_map<string, string> dict;
	dict.insert({ "sort", "排序" });
	dict.insert({ "left", "左边" });
	dict.insert({ "right", "右边" });
	
	dict["left"] = "左边,剩余";
	dict["insert"] = "插入";
	dict["string"];
	
	bit::unordered_map<string, string>::iterator it = dict.begin();
	while (it != dict.end())
	{
		// 不能修改first,可以修改second
		//it->first += 'x';
		it->second += 'x';
		
		cout << it->first << ":" << it->second << endl;
		++it;
	}
	cout << endl;
}

void test_set()
{
	bit::unordered_set<int> s;
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14, 3,3,15 };
	for (auto e : a)
	{
		s.insert(e);
	}
	
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;
	
	bit::unordered_set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		//不支持修改
		//*it += 1;
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

int main()
{
	//test_map();
	test_set();
	return 0;
}
相关推荐
山川行4 小时前
Python快速闯关8:内置函数
java·开发语言·前端·笔记·python·学习·visual studio
charlie1145141914 小时前
嵌入式C++教程实战之Linux下的单片机编程:从零搭建 STM32 开发工具链(2) —— HAL 库获取、启动文件坑位与目录搭建
linux·开发语言·c++·stm32·单片机·学习·嵌入式
问好眼4 小时前
《算法竞赛进阶指南》0x05 排序-1.电影
c++·算法·排序·信息学奥赛
CoderCodingNo4 小时前
【GESP】C++八级考试大纲知识点梳理 (6) 图论算法:最小生成树与最短路
c++·算法·图论
DeepModel4 小时前
【特征选择】嵌入法(Embedded)
人工智能·python·深度学习·算法
.YM.Z4 小时前
C++入门——缺省参数,函数重载,引用,inline函数,nullptr的介绍和使用
开发语言·c++
鸟电波4 小时前
硬件笔记——立创EDA的模块复用
笔记
今儿敲了吗5 小时前
算法复盘——前缀和
笔记·学习·算法
ulias2125 小时前
智能指针简述
开发语言·c++·算法