C++模拟实现unordered_map和unordered_set

1、实现哈希表

1.1、哈希概念

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

2、直接定址法:当关键字的范围比较集中时,直接定址法就是非常简单高效的方法,比如一组关键字都在[0,99]之间,那么我们开一个100个数的数组,每个关键字的值直接就是存储位置的下标。再比如一组关键字值都在[a,z]的小写字母,那么我们开一个26个数的数组,每个关键字acsii码-a的ascii码就是存储位置的下标。也就是说直接定址法本质就是用关键字计算出一个绝对位置或者相对位置,类似计数排序。但是直接定址法适用于数据比较集中的场景,如果数据比较分散就不合适了,这时候就需要使用除留余数法。

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

4、负载因子:假设哈希表中已经映射存储了N个值,哈希表的大小为M,那么负载因子=N/M ,负载因子有些地方也翻译为载荷因子/装载因子等,他的英文为load factor。负载因子越大,哈希冲突的概率越高,空间利用率越高;负载因子越小,哈希冲突的概率越低,空间利用率越低。

5、处理哈希冲突:实践中哈希表一般还是选择除留余数法作为哈希函数,当然哈希表无论选择什么哈希函数也避免不了冲突,那么插入数据时,如何解决冲突呢?主要有两种方法,开放定址法和拉链法/哈希桶。
开放定址法又分为:1、线性探测。2、二次探测。


1.2、开放定址法实现哈希表

1.2.1、哈希表结构

存储的数据我们直接用pair来表示,由于还需要存储状态,所以需要写一个类。然后状态我们直接用枚举类型来表示。

#pragma once
#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()
	{
		_table.resize(10);
	}
private:
	vector<HashData<K, V>> _table;
	size_t _n = 0;
};

数据直接存储在vector,方便扩容。添加一个_n成员变量,表示当前存储的数据个数,方便在插入的时候计算负载因子来判断需不需要扩容。同时实现默认构造函数给_table先开10个空间。


1.2.2、实现主要函数

先进行分析:
在这里插入图片描述
还需要注意:这里除留余数法的capacity怎么选?是选vector的size还是capacity呢?
这里是不能选vector的capacity的,因为如果除留余数法算出hashi在size后面,通过operator[hashi]去访问的时候,程序就会直接崩溃,因为之前我们模拟实现vector的时候知道:vector底层operator[]函数是直接断言assert(pos < size())的,所以这样会出问题,因此只能选择size。

bool Insert(const pair<K, V>& kv)
{
	if (Find(kv.first))
	{
		return false;
	}
	if (_n * 10 / _table.size() >= 7)
	{
		size_t newSize = _table.size() * 2;
		HashTable<K, V> newHT;
		newHT._table.resize(newSize);
		for (size_t i = 0; i < _table.size(); i++)
		{
			if (_table[i]._state == EXIST)
			{
				newHT.Insert(_table[i]._kv);
			}
		}
		_table.swap(newHT._table);
	}
	size_t hashi = kv.first % _table.size();
	while (_table[hashi]._state == EXIST)
	{
		++hashi;
		hashi %= _table.size();
	}
	_table[hashi]._kv = kv;
	_table[hashi]._state = EXIST;
	++_n;
	return true;
}

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

bool Erase(const K& key)
{
	HashData<const K, V>* ret = Find(key);
	if (ret)
	{
		ret->_state = DELETE;
		--_n;
		return true;
	}
	return false;
}

1、Insert函数这里当负载因子大于等于0.7就进行扩容,如果不扩容,那么插入效率就会变低,而且如果容量满了就会死循环。扩容我们直接创建一个新的HashTable对象,然后先开好vector的空间,复用Insert函数完成数据的重新映射。由于扩容后_table.size()发生变化,因此数据可能会被映射到新的位置。
2、Find函数返回值为HashData<const K, V>*,这是为了支持key不能修改。return (HashData<const K, V>*)&_table[hashi];找到数据需要对数据的地址进行强转,防止某些情况下出问题。


1.2.3、实现哈希函数为类模板参数

现在哈希表已经可以跑起来了,但是只能针对int类型,如果是string类型就会出错,原因是因为我们这里的哈希函数只能处理整型。
当模板参数K是int时:hashi = key % _table.size(),没有问题。
当模板参数K是string时:hashi = key % _table.size(),这是错误的。
对于其他类型,我们需要先转换成整型,然后再使用除留余数法算出hashi。

下面介绍一个字符串哈希函数:BKDR Hash Function
在这里插入图片描述
原理很简单:就是遍历字符串,每次让哈希值乘上131,然后再加上字符的ASCII码值。经过测试,这个方法可以有效的减少哈希冲突的次数。
对其他字符串哈希函数有兴趣的可以查看这篇文章:各种字符串Hash函数
哈希函数实现为仿函数,然后给哈希表添加类模板参数,同时给类模板参数缺省值,如果不传默认就是整型的哈希函数:

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

template<class K, class V, class HashFunc = DefaultHashFunc<K>>
class HashTable
{
//...
};

在这里插入图片描述
代码中涉及计算hashi的都需要先创建HashFunc对象,然后通过operator()获取哈希值。

下面再实现字符串的哈希函数,同时我们发现我们在使用unordered_map的时候,如果数据类型是string时,我们并不需要传哈希函数,这是因为类模板参数给了缺省值。我们这里如果要支持就需要使用模板的特化,代码如下:

template<>
struct DefaultHashFunc<string>
{
	size_t operator()(const string& str)
	{
		size_t hash = 0;
		for (auto ch : str)
		{
			hash *= 131;
			hash += ch;
		}
		return hash;
	}
};

这样当传入数据类型K是string时,HashFunc=DefaultHashFunc<K>,走的就是特化的仿函数。


1.2.4、完整代码

#pragma once
#include <vector>

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

template<>
struct DefaultHashFunc<string>
{
	size_t operator()(const string& str)
	{
		size_t hash = 0;
		for (auto ch : str)
		{
			hash *= 131;
			hash += ch;
		}
		return hash;
	}
};

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, class V, class HashFunc = DefaultHashFunc<K>>
	class HashTable
	{
	public:
		HashTable()
		{
			_table.resize(10);
		}

		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
			{
				return false;
			}
			if (_n * 10 / _table.size() >= 7)
			{
				size_t newSize = _table.size() * 2;
				HashTable<K, V> newHT;
				newHT._table.resize(newSize);
				for (size_t i = 0; i < _table.size(); i++)
				{
					if (_table[i]._state == EXIST)
					{
						newHT.Insert(_table[i]._kv);
					}
				}
				_table.swap(newHT._table);
			}
			HashFunc hf;
			size_t hashi = hf(kv.first) % _table.size();
			while (_table[hashi]._state == EXIST)
			{
				++hashi;
				hashi %= _table.size();
			}
			_table[hashi]._kv = kv;
			_table[hashi]._state = EXIST;
			++_n;
			return true;
		}

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

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

1.3、拉链法实现哈希表

1.3.1、哈希表结构

在这里插入图片描述
拉链法这里是一个指针数组,然后每个位置都是一条链表,插入新节点直接算出hashi然后头插即可。但是还是需要考虑扩容问题,如果不扩容的话,假设插入了一万个数据,每个桶平均下来就是一千个数据,那么删除和查找数据的时候效率就会很低,所以还是需要扩容,只不过这里负载因子可以大一些,这里的负载因子到1就扩容,平均每个桶一个数据。

template<class K, class V>
struct HashData
{
	pair<K, V> _kv;
	HashData<K, V>* _next;

	HashData(const pair<K, V>& kv)
		:_kv(kv)
		, _next(nullptr)
	{}
};

template<class K, class V>
class HashTable
{
	typedef HashData<K, V> Node;
public:
	HashTable()
	{
		_table.resize(10, nullptr);
	}
private:
	vector<Node*> _table;
	size_t _n = 0;
};

1.2.2、实现主要函数

~HashTable()
{
	for (size_t i = 0; i < _table.size(); i++)
	{
		Node* cur = _table[i];
		while (cur)
		{
			Node* next = cur->_next;
			delete cur;
			cur = next;
		}
		_table[i] = nullptr;
	}
	_n = 0;
}

bool Insert(const pair<K, V>& kv)
{
	if (Find(kv.first))
	{
		return false;
	}
	HashFunc hf;
	if (_n == _table.size())
	{
		size_t newSize = _table.size() * 2;
		vector<Node*> newTable;
		newTable.resize(newSize, nullptr);
		for (size_t i = 0; i < _table.size(); i++)
		{
			Node* cur = _table[i];
			while (cur)
			{
				Node* next = cur->_next;
				size_t hashi = hf(cur->_kv.first) % newSize;
				cur->_next = newTable[hashi];
				newTable[hashi] = cur;
				cur = next;
			}
			_table[i] = nullptr;
		}
		_table.swap(newTable);
	}
	size_t hashi = hf(kv.first) % _table.size();
	Node* newnode = new Node(kv);
	newnode->_next = _table[hashi];
	_table[hashi] = newnode;
	++_n;
	return true;
}

Node* Find(const K& key)
{
	HashFunc hf;
	size_t hashi = hf(key) % _table.size();
	Node* cur = _table[hashi];
	while (cur)
	{
		if (cur->_kv.first == key)
		{
			return cur;
		}
		cur = cur->_next;
	}
	return nullptr;
}

bool Erase(const K& key)
{
	HashFunc hf;
	size_t hashi = hf(key) % _table.size();
	Node* prev = nullptr;
	Node* cur = _table[hashi];
	while (cur)
	{
		if (cur->_kv.first == key)
		{
			if (prev == nullptr)
			{
				_table[hashi] = cur->_next;
			}
			else
			{
				prev->_next = cur->_next;
			}
			delete cur;
			--_n;
			return true;
		}
		prev = cur;
		cur = cur->_next;
	}
	return false;
}

void Print()
{
	for (size_t i = 0; i < _table.size(); i++)
	{
		printf("[%d]->", i);
		Node* cur = _table[i];
		while (cur)
		{
			cout << cur->_kv.first << ":" << cur->_kv.second << "->";
			cur = cur->_next;
		}
		printf("NULL\n");
	}
	cout << endl;
}

1、这里需要实现析构函数,将链表的节点全部释放,因为这里的节点是从堆上申请的。前面的开放定址法就不需要。
2、这里的插入函数不复用Insert,如果复用Insert就需要不断的创建节点,但是实际上并不需要重新创建节点,直接把之前的节点拿过来重新映射到新位置即可。
3、Erase函数需要保存前一个节点的指针,方便修改链接关系。
4、实现Print函数帮助我们测试程序。


1.2.3、完整代码

#pragma once
#include <vector>

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

template<>
struct DefaultHashFunc<string>
{
	size_t operator()(const string& str)
	{
		size_t hash = 0;
		for (auto ch : str)
		{
			hash *= 131;
			hash += ch;
		}
		return hash;
	}
};

namespace hash_bucket
{
	template<class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		HashData<K, V>* _next;

		HashData(const pair<K, V>& kv)
			:_kv(kv)
			, _next(nullptr)
		{}
	};

	template<class K, class V, class HashFunc = DefaultHashFunc<K>>
	class HashTable
	{
		typedef HashData<K, V> Node;
	public:
		HashTable()
		{
			_table.resize(10, nullptr);
		}

		~HashTable()
		{
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_table[i] = nullptr;
			}
		}

		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
			{
				return false;
			}
			HashFunc hf;
			if (_n == _table.size())
			{
				size_t newSize = _table.size() * 2;
				vector<Node*> newTable;
				newTable.resize(newSize, nullptr);
				for (size_t i = 0; i < _table.size(); i++)
				{
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;
						size_t hashi = hf(cur->_kv.first) % newSize;
						cur->_next = newTable[hashi];
						newTable[hashi] = cur;
						cur = next;
					}
					_table[i] = nullptr;
				}
				_table.swap(newTable);
			}
			size_t hashi = hf(kv.first) % _table.size();
			Node* newnode = new Node(kv);
			newnode->_next = _table[hashi];
			_table[hashi] = newnode;
			++_n;
			return true;
		}

		Node* Find(const K& key)
		{
			HashFunc hf;
			size_t hashi = hf(key) % _table.size();
			Node* cur = _table[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			return nullptr;
		}

		bool Erase(const K& key)
		{
			HashFunc hf;
			size_t hashi = hf(key) % _table.size();
			Node* prev = nullptr;
			Node* cur = _table[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					if (prev == nullptr)
					{
						_table[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					--_n;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}

		void Print()
		{
			for (size_t i = 0; i < _table.size(); i++)
			{
				printf("[%d]->", i);
				Node* cur = _table[i];
				while (cur)
				{
					cout << cur->_kv.first << ":" << cur->_kv.second << "->";
					cur = cur->_next;
				}
				printf("NULL\n");
			}
			cout << endl;
		}
	private:
		vector<Node*> _table;
		size_t _n = 0;
	};
}

2、模拟实现unordered_map和unordered_set

2.1、实现复用哈希表的框架并支持insert

底层我们使用的是哈希桶,这里基本上和前面封装复用红黑树差不多,不懂可以去看看模拟实现map和set这一节,直接上代码。

// UnOrderedMap.h
#pragma once
#include "HashTable.h"

namespace zzy
{
	template<class K, class V>
	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;
	};
}
//UnOrderedSet.h
#pragma once
#include "HashTable.h"

namespace zzy
{
	template<class 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;
	};
}
// HashTable.h
namespace hash_bucket
{
	template<class T>
	struct HashData
	{
		T _data;
		HashData<T>* _next;

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

	template<class K, class T, class KeyOfT, class HashFunc = DefaultHashFunc<K>>
	class HashTable
	{
		typedef HashData<T> Node;
	public:
		HashTable()
		{
			_table.resize(10, nullptr);
		}

		~HashTable()
		{
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_table[i] = nullptr;
			}
		}

		bool Insert(const T& data)
		{
			KeyOfT kot;
			if (Find(kot(data)))
			{
				return false;
			}
			HashFunc hf;
			if (_n == _table.size())
			{
				size_t newSize = _table.size() * 2;
				vector<Node*> newTable;
				newTable.resize(newSize, nullptr);
				for (size_t i = 0; i < _table.size(); i++)
				{
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;
						size_t hashi = hf(kot(cur->_data)) % newSize;
						cur->_next = newTable[hashi];
						newTable[hashi] = cur;
						cur = next;
					}
					_table[i] = nullptr;
				}
				_table.swap(newTable);
			}
			size_t hashi = hf(kot(data)) % _table.size();
			Node* newnode = new Node(data);
			newnode->_next = _table[hashi];
			_table[hashi] = newnode;
			++_n;
			return true;
		}

		Node* Find(const K& key)
		{
			HashFunc hf;
			KeyOfT kot;
			size_t hashi = hf(key) % _table.size();
			Node* cur = _table[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			return nullptr;
		}

		bool Erase(const K& key)
		{
			HashFunc hf;
			KeyOfT kot;
			size_t hashi = hf(key) % _table.size();
			Node* prev = nullptr;
			Node* cur = _table[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					if (prev == nullptr)
					{
						_table[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					--_n;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}
	private:
		vector<Node*> _table;
		size_t _n = 0;
	};
}

2.2、实现哈希表的迭代器

先进行分析:
在这里插入图片描述
先把基础框架写出来:
由于需要HashTable*,所以需要把哈希表的所有类模板参数都拿过来。

template<class K, class T, class KeyOfT, class HashFunc>
struct HTIterator
{
	typedef HashData<T> Node;
	typedef HTIterator<K, T, KeyOfT, HashFunc> Self;

	Node* _node;
	HashTable<K, T, KeyOfT, HashFunc>* _pht;

	HTIterator(Node* node, HashTable<K, T, KeyOfT, HashFunc>* pht)
		:_node(node)
		,_pht(pht)
	{}

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

	T* operator->()
	{
		return &(operator*());
	}

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

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

下面实现operator++:

Self& operator++()
{
	if (_node->_next)
	{
		_node = _node->_next;
	}
	else
	{
		KeyOfT kot;
		HashFunc hf;
		size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();
		++hashi;
		while (hashi < _pht->_table.size())
		{
			if (_pht->_table[hashi])
			{
				_node = _pht->_table[hashi];
				return *this;
			}
			++hashi;
		}
		_node = nullptr;
	}
	return *this;
}

如果下一个节点存在,直接往后走。否则计算出hashi,然后往后遍历直到找到有数据的桶,如果找不到就返回nullptr,表示end(),那第一个数据就是begin()。


在哈希表中加入迭代器:

typedef HTIterator<K, T, KeyOfT, HashFunc> iterator;

iterator begin()
{
	for (size_t i = 0; i < _table.size(); i++)
	{
		Node* cur = _table[i];
		if (cur)
		{
			return iterator(cur, this);
		}
	}
	return iterator(nullptr, this);
}

iterator end()
{
	return iterator(nullptr, this);
}

这里直接用this指针去构造迭代器对象。


在unordered_map和unordered_set中加入迭代器:

typedef typename hash_bucket::HashTable<K, pair<K, V>, MapKeyOfT>::iterator iterator;

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

iterator end()
{
	return _ht.end();
}
typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::iterator iterator;

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

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

接着进行测试,运行后发现编译报错:
在这里插入图片描述
这是因为我们的迭代器是写在哈希表前面的,这时候哈希表要用迭代器是没问题的,因为它向上找可以找到迭代器,但是迭代器要用哈希表向上找找不到,存在相互依赖问题,所以我们需要在迭代器前面声明哈希表。

在这里插入图片描述


编译后还是报错:
在这里插入图片描述
这是由于我们在迭代器中访问了哈希表的私有成员变量,但是哈希表的私有成员变量在类外是不能被访问的,所以这里我们需要让HTIterator成为HashTable的友元类。
在这里插入图片描述


实现哈希表的const迭代器:跟之前一样,通过Ref和Ptr来控制类模板实例化出不同的类。

在这里插入图片描述
在哈希表中加入const迭代器:

typedef HTIterator<K, T, const T&, const T*, KeyOfT, HashFunc> const_iterator;
const_iterator begin() const
{
	for (size_t i = 0; i < _table.size(); i++)
	{
		Node* cur = _table[i];
		if (cur)
		{
			return const_iterator(cur, this);
		}
	}
	return const_iterator(nullptr, this);
}

const_iterator end() const
{
	return const_iterator(nullptr, this);
}

2.3、解决key不可修改问题

unordered_set处理方式是不管是普通迭代器还是const迭代器,本质上都是const迭代器,这样key就无法修改了。
unordered_map的处理方式是给key加const,这样key就无法修改了,但是value还可以修改。

typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator const_iteraotr;

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

iterator end() const
{
	return _ht.end();
}
typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::const_iterator 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();
}

编译后发现问题:
在这里插入图片描述
在这里插入图片描述
如果加一个cont Table*的构造函数也是不行的,因为成员变量_pht还是HashTable*的。所以这里解决办法就是直接将_pht声明为const HashTable*,反正迭代器里面也不会对HashTable进行修改,然后再支持一个构造函数即可。
在这里插入图片描述


2.4、解决insert返回值问题并实现operator[]

将哈希表中的Insert函数返回值修改为pair<iterator,bool>,由于在Insert中复用了Find函数,所以Find函数返回值要修改成iterator。然后在unordered_map和unordered_set中修改代码如下:

// UnOrderedSet.h
pair<iterator, bool> insert(const K& key)
{
	pair<typename hash_bucket::HashTable<K, K, SetKeyOfT>::iterator, bool> ret = _ht.Insert(key);
	return pair<iterator, bool>(ret.first, ret.second);
}
// UnOrderedMap.h
pair<iterator, bool> insert(const pair<K, V>& kv)
{
	return _ht.Insert(kv);
}

然后就会出现普通迭代器向const迭代器转换的问题,分析如下:
在这里插入图片描述
在迭代器中加入如下代码即可解决:

typedef HTIterator<K, T, T&, T*, KeyOfT, HashFunc> Iterator;
HTIterator(const Iterator& it)
	:_node(it._node)
	, _pht(it._pht)
{}

无论类模板被实例化为const迭代器还是普通迭代器,Iterator都是普通迭代器。
1、当类模板被实例化为普通的迭代器,就是拷贝构造函数。
2、当类模板被实例化为const迭代器,支持普通迭代器构造const迭代器。

同时注意到:当实例化为const迭代器时,Iterator和const迭代器是不同的类,如果写成class在类外是不能访问Iterator中的成员变量的,也就是it._node和it._pht都无法访问,所以这里写成struct。


Insert返回值修改后,就可以实现unordered_map的operator[]函数了:

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

2.5、完整代码

// UnOrderedSet.h
#pragma once
#include "HashTable.h"

namespace zzy
{
	template<class K>
	class unordered_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator iterator;
		typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator const_iteraotr;

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

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

		pair<iterator, bool> insert(const K& key)
		{
			pair<typename hash_bucket::HashTable<K, K, SetKeyOfT>::iterator, bool> ret = _ht.Insert(key);
			return pair<iterator, bool>(ret.first, ret.second);
		}

	private:
		hash_bucket::HashTable<K, K, SetKeyOfT> _ht;
	};
}
// UnOrderedMap.h
#pragma once
#include "HashTable.h"

namespace zzy
{
	template<class K, class V>
	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>::iterator iterator;
		typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::const_iterator 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;
		}
	private:
		hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT> _ht;
	};
}
// HashTable.h
#pragma once
#include <vector>

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

template<>
struct DefaultHashFunc<string>
{
	size_t operator()(const string& str)
	{
		size_t hash = 0;
		for (auto ch : str)
		{
			hash *= 131;
			hash += ch;
		}
		return hash;
	}
};

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, class V, class HashFunc = DefaultHashFunc<K>>
	class HashTable
	{
	public:
		HashTable()
		{
			_table.resize(10);
		}

		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
			{
				return false;
			}
			if (_n * 10 / _table.size() >= 7)
			{
				size_t newSize = _table.size() * 2;
				HashTable<K, V> newHT;
				newHT._table.resize(newSize);
				for (size_t i = 0; i < _table.size(); i++)
				{
					if (_table[i]._state == EXIST)
					{
						newHT.Insert(_table[i]._kv);
					}
				}
				_table.swap(newHT._table);
			}
			HashFunc hf;
			size_t hashi = hf(kv.first) % _table.size();
			while (_table[hashi]._state == EXIST)
			{
				++hashi;
				hashi %= _table.size();
			}
			_table[hashi]._kv = kv;
			_table[hashi]._state = EXIST;
			++_n;
			return true;
		}

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

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

namespace hash_bucket
{
	template<class T>
	struct HashData
	{
		T _data;
		HashData<T>* _next;

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

	template<class K, class T, class KeyOfT, class HashFunc>
	class HashTable;

	template<class K, class T, class Ref, class Ptr, class KeyOfT, class HashFunc>
	struct HTIterator
	{
		typedef HashData<T> Node;
		typedef HTIterator<K, T, Ref, Ptr, KeyOfT, HashFunc> Self;
		typedef HTIterator<K, T, T&, T*, KeyOfT, HashFunc> Iterator;

		Node* _node;
		const HashTable<K, T, KeyOfT, HashFunc>* _pht;

		HTIterator(Node* node, HashTable<K, T, KeyOfT, HashFunc>* pht)
			:_node(node)
			,_pht(pht)
		{}

		HTIterator(Node* node, const HashTable<K, T, KeyOfT, HashFunc>* pht)
			:_node(node)
			, _pht(pht)
		{}

		HTIterator(const Iterator& it)
			:_node(it._node)
			, _pht(it._pht)
		{}

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

		Ptr operator->()
		{
			return &(operator*());
		}

		Self& operator++()
		{
			if (_node->_next)
			{
				_node = _node->_next;
			}
			else
			{
				KeyOfT kot;
				HashFunc hf;
				size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();
				++hashi;
				while (hashi < _pht->_table.size())
				{
					if (_pht->_table[hashi])
					{
						_node = _pht->_table[hashi];
						return *this;
					}
					++hashi;
				}
				_node = nullptr;
			}
			return *this;
		}

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

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

	template<class K, class T, class KeyOfT, class HashFunc = DefaultHashFunc<K>>
	class HashTable
	{
		typedef HashData<T> Node;
		template<class K, class T, class Ref, class Ptr, class KeyOfT, class HashFunc>
		friend struct HTIterator;
	public:
		typedef HTIterator<K, T, T&, T*, KeyOfT, HashFunc> iterator;
		typedef HTIterator<K, T, const T&, const T*, KeyOfT, HashFunc> const_iterator;

		iterator begin()
		{
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				if (cur)
				{
					return iterator(cur, this);
				}
			}
			return iterator(nullptr, this);
		}

		iterator end()
		{
			return iterator(nullptr, this);
		}

		const_iterator begin() const
		{
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				if (cur)
				{
					return const_iterator(cur, this);
				}
			}
			return const_iterator(nullptr, this);
		}

		const_iterator end() const
		{
			return const_iterator(nullptr, this);
		}

		HashTable()
		{
			_table.resize(10, nullptr);
		}

		~HashTable()
		{
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_table[i] = nullptr;
			}
		}

		pair<iterator, bool> Insert(const T& data)
		{
			KeyOfT kot;
			iterator it = Find(kot(data));
			if (it != end())
			{
				return make_pair(it, false);
			}
			HashFunc hf;
			if (_n == _table.size())
			{
				size_t newSize = _table.size() * 2;
				vector<Node*> newTable;
				newTable.resize(newSize, nullptr);
				for (size_t i = 0; i < _table.size(); i++)
				{
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;
						size_t hashi = hf(kot(cur->_data)) % newSize;
						cur->_next = newTable[hashi];
						newTable[hashi] = cur;
						cur = next;
					}
					_table[i] = nullptr;
				}
				_table.swap(newTable);
			}
			size_t hashi = hf(kot(data)) % _table.size();
			Node* newnode = new Node(data);
			newnode->_next = _table[hashi];
			_table[hashi] = newnode;
			++_n;
			return make_pair(iterator(newnode, this), true);
		}

		iterator Find(const K& key)
		{
			HashFunc hf;
			KeyOfT kot;
			size_t hashi = hf(key) % _table.size();
			Node* cur = _table[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return iterator(cur, this);
				}
				cur = cur->_next;
			}
			return end();
		}

		bool Erase(const K& key)
		{
			HashFunc hf;
			KeyOfT kot;
			size_t hashi = hf(key) % _table.size();
			Node* prev = nullptr;
			Node* cur = _table[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					if (prev == nullptr)
					{
						_table[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					--_n;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}
	private:
		vector<Node*> _table;
		size_t _n = 0;
	};
}
C++中,unordered_set unordered_map 都是基于哈希表实现的无序关联容器,它们之间的主要区别在于存储的数据类型使用的场景,具体区别如下: 1. **键值对 vs. 单一值**:unordered_set 存储单一值,每个元素独一无二;而 unordered_map 存储键值对,键具有唯一性,通过键来访问对应的值。例如,若要存储一系列不重复的整数,可使用 unordered_set;若要存储学生姓名对应成绩,就需要 unordered_map,其中姓名为键,成绩为值 [^1]。 2. **查找方式**:由于存储结构不同,查找方式也有差异。unordered_set 直接查找元素是否存在;unordered_map 则通过键查找对应的值。示例代码如下: ```cpp #include <iostream> #include <unordered_set> #include <unordered_map> int main() { // unordered_set示例 std::unordered_set<int> mySet = {1, 2, 3, 4, 5}; auto setIt = mySet.find(3); if (setIt != mySet.end()) { std::cout << "unordered_set中找到了元素3" << std::endl; } // unordered_map示例 std::unordered_map<std::string, int> myMap = {{"apple", 1}, {"banana", 2}, {"cherry", 3}}; auto mapIt = myMap.find("banana"); if (mapIt != myMap.end()) { std::cout << "unordered_map中找到了键banana,对应的值是: " << mapIt->second << std::endl; } return 0; } ``` 3. **使用场景**:当只需要判断元素是否存在,不需要关联其他信息时,unordered_set 是更好的选择;而当需要存储元素并且要关联额外信息时,unordered_map 更合适。例如,在处理单词去重时,用 unordered_set 存储单词;在统计单词出现次数时,用 unordered_map,键为单词,值为出现次数 [^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值