C++:哈希

哈希

unordered_set和unordered_map

unordered_set

set和unordered_set的功能高度相似,只是底层结构不同。

unordered_set与set的差异

  1. unordered_set和set的第⼀个差异是对key的要求不同,set要求Key支持小于比较,而unordered_set要求Key支持转成整形且支持等于比较
  2. unordered_set和set的第⼆个差异是迭代器的差异,set的iterator是双向迭代器,unordered_set是单向迭代器 ,其次set底层是红黑树,红黑树是⼆叉搜索树,⾛中序遍历是有序的,所以set迭代器遍历是有序+去重。而unordered_set底层是哈希表,迭代器遍历是无序+去重
  3. unordered_set和set的第三个差异是性能的差异,整体而言大多数场景下,unordered_set的增删查改更快一些 ,因为红黑树增删查改效率是O(logN) ,而哈希表增删查平均效率是O(1)
unordered_map

map和unordered_map也是高度相似,只有些许差异。

  1. map要求Key支持小于比较,而unordered_map要求Key支持转成整形且支持等于比较
  2. map的iterator是双向迭代器,unordered_map是单向迭代器 ,map底层是红黑树,unordered_map底层是哈希表,迭代器遍历是 Key无序+去重
  3. 大多数场景下,unordered_map的增删查改更快一些 ,因为红黑树增删查改效率是O(logN) ,而哈希表增删查平均效率是 O(1)
代码使用
cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<unordered_set>
#include<unordered_map>
using namespace std;

int main()
{
	unordered_set<int> s = { 3,1,6,7,8,2,1,1,5,6,7,6 };
	unordered_set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	return 0;
}

unordered_multiset/unordered_multimap

unordered_multiset/unordered_multimap 与 multiset/multimap也是高度相似,支持Key冗余。差异也如上。

哈希概念

哈希(hash)又称散列,是⼀种组织数据的方式。本质就是通过哈希函数把关键字Key跟存储位置建立一个映射关系,查找时通过这个哈希函数计算出Key存储的位置,进行快速查找。

直接定址法

当关键字的范围比较集中时,如一组关键字都在[0,99]之间,那么开一个100个数的数组,每个关键字的值直接就是存储位置的下标。再比如一组关键字值都在[a,z]的小写字母,那么我们开⼀个26个数的数组,每个关键字ASCII码 - 字符a 得到的值就是存储位置的下标。也就是说直接定址法本质即用关键字计算出一个绝对位置 或者 相对位置 。(比如计数排序)

哈希冲突

使用直接定址法,当关键字的范围比较分散时,会很浪费内存甚至内存不够用。假设我们只有数据范围是[0, 9999]的N个值,要映射到⼀个M个空间的数组中(⼀般M >= N ),那么就要借助哈希函数h(x),关键字key被放到数组的h(key)位置,注意,h(key)计算出的值必须在[0, M)之间

这里存在的⼀个问题是,两个不同的key可能会映射到同一个位置 ,这种问题即哈希冲突,或者哈希碰撞。理想情况是找出⼀个好的哈希函数避免冲突,但是实际场景中,冲突是不可避免的,所以我们尽可能设计出优秀的哈希函数,减少冲突的次数,同时也要去设计出解决冲突的方案。

负载因子

假设哈希表中已经映射存储了N个值,哈希表的大小为M,那么 负载因子=N/M负载因子越大,哈希冲突的概率越高,空间利用率越高;负载因子越小,哈希冲突的概率越低,空间利用率越低

将关键字转为size_t

将关键字映射到数组中位置,⼀般是整数好做映射计算,如果不是整数,我们要想办法转换成整数。源码中用仿函数Hash把key转换成size_t 。如果遇到像string、Date无法强转的,则自己实现,string较为常用,可以特化模板。

cpp 复制代码
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) //可以有效避免"abcd","bcad"冲突的情况
		{
			hash += e;
			hash *= 131;
		}
		return hash;
	}
};

哈希函数

⼀个好的哈希函数应该让N个关键字被等概率且均匀地散列分布到哈希表的M个空间中,实际中很难做到,但是要尽量往这个方向去考量设计。

除法散列法/除留余数法

除法散列法 也叫除留余数法,假设哈希表的大小为M,key除以M的余数作为映射位置的下标 ,即哈希函数为:h(key) = key % M

使用除法散列法时,要尽量避免M为某些值 ,如2的幂10的幂 等。如果是 2x,key % 2x 本质相当于保留key的后X位(把key转成二进制表示的位),那么后x位相同的值,计算出的哈希值都是⼀样的,就冲突了。如果是 10x,保留的都是10进值的后x位,如:{112, 12312},如果M是100,也就是102 ,那么计算出的哈希值都是12。

所以,使用除法散列法时,建议M取不太接近2的整数次幂的一个素数

sgi版本的哈希表使用的方法,给了一个近似2倍的素数表

cpp 复制代码
inline unsigned long __stl_next_prime(unsigned long n) //会频繁调用,inline修饰,减少消耗
{
	// 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);//在下标[first,last)范围内,找大于等于n的数
	return pos == last ? *(last - 1) : *pos;
}

初始时哈希表的大小M可以为 __stl_next_prime(0),即M=53,此时M既不接近25,也不接近26,同时也是素数。扩容后M=97,也实现了近似2倍扩容。

哈希函数还有其他方法,如乘法散列法、全域散列法等。

处理哈希冲突:开放定址法

在开放定址法中,所有的元素都放到哈希表,当⼀个关键字key用哈希函数计算出的位置冲突了,则按照某种规则找到一个没有存储数据的位置进行存储 。规则有三种:线性探测、⼆次探测、双重探测。开放定址法要求负载因子小于1

线性探测
  1. 从发生冲突的位置开始,依次线性向后探测,直到寻找到下一个没有存储数据的位置为止,如果走到哈希表尾,则回绕到哈希表头的位置。
  2. h(key)=hash0=key % M ,hash0位置冲突 了,则线性探测公式 为:hc(key,i)=hashi=(hash0+i) % M,i={1,2,3......M-1} 。由于负载因子小于1,哈希表中一定有空位置,所以最多探测M-1次,一定能找到一个存储key的位置。
  3. 如果hash0位置连续冲突 ,hash0,hash1,hash2位置已经存储数据 ,后续映射到hash0,hash1,hash2,hash3的值都会争夺hash3位置 ,这种现象叫做群集 /堆积。二次探测可在一定程度上改善。
二次探测
  1. 从发生冲突的位置开始,依次左右按二次方跳跃式探测,直到寻找到下⼀个没有存储数据的位置为止,如果往右走到哈希表尾,则回绕到哈希表头的位置;如果往左走到哈希表头,则回绕到哈希表尾的位置。
  2. h(key)=hash0=key % M ,hash0位置冲突 了,则二次探测公式 为:hc(key,i)=hashi=(hash0 ± i2) % M,i={1,2,3......M/2}
  3. hashi=(hash0 - i2) % M时,若hashi<0,则hashi+=M 。
开放定址法线性探测代码实现

要给每个存储位置加一个状态标识,否则删除一些值以后,会影响后面冲突的值的查找。例如删除30,查找20,用哈希函数求得映射下标为9,但是下标为9的位置已经为空,没找到,但哈希表中其实又是有20的。

给每个存储位置加一个标识状态**{EXIST,DELETE,EMPTY}。查找一个key,求得映射位置h(key),发现状态为EMPTY,说明整个哈希表中没有key**。如果哈希表的其他位置存在key,表明因为冲突,h(key)位置已经被别的值占了,则h(key)位置的状态应是EXIST或DELETE,矛盾,所以整个哈希表中绝对没有key。

控制负载因子到0.7时扩容

如果key不能取模则用仿函数转成size_t。

cpp 复制代码
#include<vector>
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>
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto& e : s)
		{
			hash += e;
			hash *= 131;
		}
		return hash;
	}
};

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

namespace open_address
{
	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
	public:
		HashTable()
			:_tables(__stl_next_prime(0))
			, _n(0)
		{
		}

		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 (auto& e : _tables)
				{
					if (e._state == EXIST)
						newht.Insert(e._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)
			{
				ret->_state = DELETE;
				return true;
			}
			else
				return false;
		}

	private:
		vector<HashData<K, V>> _tables;
		size_t _n;//记录数据个数
	};
}

测试

cpp 复制代码
#include"HashTable.h"
int main()
{
	open_address::HashTable<int, int> ht;
	int a[] = { 19,30,5,36,13,20,21,12 };
	for (auto e : a)
	{
		ht.Insert({ e,e });
	}

	if (ht.Find(13))
		ht.Erase(13);
	else
		cout << "没找到" << endl;

	return 0;
}

如果key为日期类,则要自己实现仿函数。

cpp 复制代码
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()
{
	string a1[] = { "sort","insert","abcd","bcad","aadd"};
	open_address::HashTable<string, string> ht;
	for (auto& e : a1)
		ht.Insert({ e,e });

	int a2[] = { -19,-30,5,36,13,20,21,12 };
	open_address::HashTable<int, int> ht1;
	for (auto& e : a2)
		ht1.Insert({ e,e });

	open_address::HashTable<Date, int, DateHashFunc> ht2;
	ht2.Insert({ {2026,1,2},1 });
	ht2.Insert({ {2026,2,1},2 });

	return 0;
}

处理哈希冲突:链地址法

开放定址法中所有的元素都放到哈希表里,链地址法中所有的数据不再直接存储在哈希表中,哈希表中存储一个指针 ,没有数据映射这个位置时,这个指针为空,有多个数据映射到这个位置时,则把这些冲突的数据链接成一个链表 ,挂在哈希表这个位置下面,链地址法也叫做拉链法或者哈希桶

链地址法对负载因子没有要求,我们把负载因子控制在1,如果某个桶特别长时,当桶的长度大于8,就把链表转成红黑树。

代码实现
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,class V,class Hash=HashFunc<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;
	public:
		HashTable()
			:_tables(__stl_next_prime(0))
			,_n(0)
		{ }

		//拷贝构造
		HashTable(const HashTable<K, V, Hash>& ht)
		{
			_tables.resize(ht._tables.size());
			for (int i = 0;i < ht._tables.size();i++)
			{
				Node* htcur = ht._tables[i];
				while (htcur)
				{
					Node* newnode = new Node(htcur->_kv);
					//尾插
					Node* cur = _tables[i];
					if (cur == nullptr)
						_tables[i] = newnode;
					else
					{
						while (cur->_next)
						{
							cur = cur->_next;
						}
						cur->_next = newnode;
					}

					htcur = htcur->_next;
				}
			}
			_n = ht._n;
		}

		~HashTable()
		{
			for (int 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 hash;
			if (_n == _tables.size())//扩容
			{
				vector<Node*> newTable(__stl_next_prime(_tables.size() + 1));
				for (int i = 0;i < _tables.size();i++)
				{
					Node* cur = _tables[i];
					
					while (cur)
					{
						Node* next = cur->_next;
						size_t hashi = hash(kv.first) % newTable.size();//找到新表的映射位置
						//头插
						cur->_next = newTable[hashi];
						newTable[hashi] = cur;

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

			size_t hashi = hash(kv.first) % _tables.size();
			Node* newnode = new Node(kv);
			//头插
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;

			return true;
		}

		Node* Find(const K& key)
		{
			Hash hash;
			size_t hashi = hash(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 hash;
			size_t hashi = hash(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;
				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			return false;
		}

	private:
		vector<Node*> _tables;
		size_t _n = 0;
	};
}

测试

cpp 复制代码
int main()
{
	int a[] = { 19,30,5,36,13,20,21,12,24,96 };
	hash_bucket::HashTable<int, int> ht;
	for (auto& e : a)
	{
		ht.Insert({ e,e });
	}
	ht.Insert({ 100,100 });
	ht.Insert({ 101,101 });

	cout << ht.Find(19) << endl;
	cout << ht.Find(36) << endl;
	cout << ht.Find(96) << endl;
	cout << ht.Find(101) << endl << endl;

	ht.Erase(19);
	ht.Erase(36);
	ht.Erase(96);
	ht.Erase(101);

	cout << ht.Find(19) << endl;
	cout << ht.Find(36) << endl;
	cout << ht.Find(96) << endl;
	cout << ht.Find(101) << endl << endl;

	return 0;
}

哈希表封装unordered_set和unordered_map

与红黑树封装set和map类似,HashTable也实现了泛型,其中HashTable用链地址法实现,仿函数KeyOfT取出T类型对象的Key对象,仿函数Hash转成size_t 。

代码实现
cpp 复制代码
//HashTable.h
#include<vector>
#include<string>
using namespace std;

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

};

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

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;

	template<class K,class T,class Ref,class Ptr,class KeyOfT,class Hash>
	struct HTIterator
	{
		typedef HashNode<T> Node;
		typedef HashTable<K, T, KeyOfT, Hash> HT;
		typedef HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;

		Node* _node;
		const HT* _ht;//用在operator++

		HTIterator(Node* node, const HT* ht)
			:_node(node)
			,_ht(ht)
		{ }

		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)) % _ht->_tables.size();//当前桶的位置
				hashi++;
				while (hashi < _ht->_tables.size())
				{
					_node = _ht->_tables[hashi];
					if (_node)
						break;
					else
						hashi++;
				}
				if (hashi == _ht->_tables.size())
					_node = nullptr;
			}
			return *this;
		}

	};

	template<class K,class T,class KeyOfT,class Hash=HashFunc<T>> //T本身是K,或含有K
	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()
		{
			if (_n == 0)
				return End();
			for (int 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 (int 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);
		}

		HashTable()
			:_tables(__stl_next_prime(0))
			,_n(0)
		{ }


		~HashTable()
		{
			for (int 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;
			auto it = Find(kot(data));
			if (it != End())
				return { it,false };

			Hash hash;
			if (_n == _tables.size())//扩容
			{
				vector<Node*> _newTable;
				_newTable.resize(__stl_next_prime(_tables.size() + 1));

				for (int i = 0;i < _tables.size();i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;
						size_t hashi = hash(kot(cur->_data)) % _newTable.size();//找到新表的映射位置
						//头插
						cur->_next = _newTable[hashi];
						_newTable[hashi] = cur;

						cur = next;
					}
				}
				_tables.swap(_newTable);
			}

			size_t hashi = hash(kot(data)) % _tables.size();
			Node* newnode = new Node(data);
            //头插
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			_n++;

			return { Iterator(newnode,this),true };
		}

		Iterator Find(const K& key)
		{
			KeyOfT kot;
			Hash hash;
			size_t hashi = hash(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
					return { cur,this };
				cur = cur->_next;
			}
			return End();
		}

		bool Erase(const K& key)
		{
			Hash hash;
			KeyOfT kot;
			size_t hashi = hash(key) % _tables.size();
			Node* cur = _tables[hashi];
			Node* prev = nullptr;
			while (cur)
			{
				if (kot(cur->_data) == key)//找到了
				{
					if (prev == nullptr)//待删除节点为头节点
					{
						_tables[hashi] = cur->_next;
					}
					else //待删除节点为中间节点
					{
						prev->_next = cur->_next;
					}
					delete cur;
					--_n;
					return true;
				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			return false;
		}

	private:
		vector<Node*> _tables;
		size_t _n=0;
	};
}

//UnorderedSet.h
#include"HashTable.h"
namespace mine
{
	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();
		}
		bool erase(const K& key)
		{
			return _ht.Erase(key);
		}

	private:
		hash_bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht;
	};

	void print(const unordered_set<int>& us)//测试const迭代器
	{
		unordered_set<int>::const_iterator cit = us.begin();
		cout << typeid(us).name() << endl;
		while (cit != us.end())
		{
			cout << *cit << " ";
			++cit;
		}
		cout << endl;
		for (auto e : us)
		{
			cout << e << " ";
		}
		cout << endl << endl;
	}
}

//UnorderedMap.h
#include"HashTable.h"
namespace mine
{
	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();
		}

		V& operator[](const K& key)
		{
			auto ret = insert({ key,V() });
			return ret.first->second;
		}

		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.Erase(key);
		}
	private:
		hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
	};

	void print(const unordered_map<string, string>& um)//测试const迭代器
	{
		unordered_map<string, string>::const_iterator cit = um.begin();
		while (cit != um.end())
		{
			cout << cit->first << ":" << cit->second << endl;
			++cit;
		}
		cout << endl;
		for (auto& e : um)
		{
			cout << e.first << ":" << e.second << endl;
		}
	}
}

测试mine::unordered_set

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

int main()
{
	int a[] = { 3,11,86,7,88,82,1,881,5,6,7,6 };
	mine::unordered_set<int> us;
	for (auto e : a)
	{
		us.insert(e);
	}
	auto it = us.begin();
	while (it != us.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	for (auto& e : us)
	{
		cout << e << " ";
	}
	cout << endl << endl;

	mine::print(us);
 
 	return 0;
}

测试mine::unordered_map

cpp 复制代码
int main()
{
	mine::unordered_map<string, string> dict;
	dict.insert({ "sort","排序" });
	dict.insert({ "字符串","string" });
	dict.insert({ "left","左" });
	dict.insert({ "right","右" });

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

	dict["left"] = "左,剩余";
	dict["insert"] = "插入";
	dict["string"];

	auto it = dict.begin();
	while (it != dict.end())
	{
		//it->second += "x";
		cout << it->first << ":" << it->second << endl;
		++it;
	}
	cout << endl;

	print(dict);

	return 0;
}
相关推荐
闻缺陷则喜何志丹2 小时前
【高等数学】导数与微分
c++·线性代数·算法·矩阵·概率论
智者知已应修善业2 小时前
【项目配置时间选择自己还是团体】2025-3-31
c语言·c++·经验分享·笔记·算法
闻缺陷则喜何志丹2 小时前
【分组背包】P12316 [蓝桥杯 2024 国 C] 循环位运算|普及+
c++·算法·蓝桥杯·洛谷·分组背包
坚持就完事了2 小时前
Java各种命名规则
java·开发语言
白露与泡影2 小时前
2026年Java面试题精选(涵盖所有Java核心面试知识点),立刻收藏
java·开发语言
24白菜头2 小时前
2026-2-9:LeetCode每日一题(动态规划专项)
数据结构·笔记·学习·算法·leetcode
BOTTLE_平2 小时前
C++图论全面解析:从基础概念到算法实践
c++·算法·图论
Lenyiin2 小时前
《 C++ 修炼全景指南:二十四 》彻底攻克图论!轻松解锁最短路径、生成树与高效图算法
c++·算法·图论·邻接表·邻接矩阵·最小生成树·最短路径
瓦特what?2 小时前
冒 泡 排 序
开发语言·数据结构·c++