【C++】封装unordered_map和unordered_set(用哈希桶实现)

前言:
       前面我们学习了unordered_map和unordered_set容器,比较了他们和map、set的查找效率,我们发现他们的效率比map、set高,进而我们研究他们的底层是由哈希实现。哈希是一种直接映射的方式,所以查找的效率很快。与学习红黑树和map、set的思路一样,我们现在学完了unordered_map和unordered_set,本章将模拟实现底层结构来封装该容器!

    作者建议在阅读本章前,可以先去看一下前面的红黑树封装map和set——红黑树封装map和set

这两篇文章都重在强调泛型编程的思想,上一篇由于是初认识,作者讲解的会更详细一点~

目录

(一)如何复用一个哈希桶

1、结点的定义:

2、两个容器各自的模板参数类型​编辑

3、改造哈希桶

(二)哈希桶的迭代器的模拟实现

1、begin()和end()的模拟实现

2、operator*和operator->及operator!=和operator==的模拟实现 

3、operator ++的模拟实现

(三)迭代器和改造哈希桶的总代码

(四)封装unordered_map和unordered_set


(一)如何复用一个哈希桶

我们学习过知道,unordered_map和unordered_set容器存放的结点并不一样,为了让它得到复用我们就需要对哈希桶进行改造,将哈希桶改造的更加泛型一点,既符合Key模型,也符合Key_Value模型。

1、结点的定义:

 所以我们这里还是和封装map和set时一样,无论是Key还是Key_Value,都用一个类型T来接收,这里高维度的泛型哈希表中,实现还是用的是Kye_Value模型,K是不能省略的,同样的查找和删除要用,故我们可以引出两个容器各自模板参数类型。


2、两个容器各自的模板参数类型

如何取到想要的数据:

  • 我们给每个容器配一个仿函数
  • 各传不同的仿函数,拿到想要的不同的数据

同时我们再给每个容器配一个哈希函数。

3、改造哈希桶

通过上面1和2,我们可以把各自存放的数据泛化成data:

这样我们哈希桶的模板参数算是完成了

  • 哈希函数我们可以自由选择并传
  • 仿函数在各自容器的封装中实现,用于比较时我们可以取出各自容器想要的数据

我们把上一篇文章封装的哈希桶拿来改造:

//K --> 键值Key,T --> 数据
//unordered_map ->HashTable<K, pair<K, V>, MapKeyOfT> _ht;
//unordered_set ->HashTable<K, K, SetKeyOfT> _ht;
template<class K, class T, class KeyOfT, class HashFunc>
class HashTable
{
	template<class K, class T, class KeyOfT, class HashFunc>
	friend class __HTIterator;

	typedef HashNode<T> Node;
public:
	typedef __HTIterator<K, T, KeyOfT, HashFunc> iterator;

	iterator begin()
	{
		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);
	}

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

			_tables[i] = nullptr;
		}
	}

	size_t GetNextPrime(size_t prime)
	{
		const int PRIMECOUNT = 28;
		static const size_t primeList[PRIMECOUNT] =
		{
			53,         97,         193,       389,       769,
			1543,       3079,       6151,      12289,     24593,
			49157,      98317,      196613,    393241,    786433,
			1572869,    3145739,    6291469,   12582917,  25165843,
			50331653,   100663319,  201326611, 402653189, 805306457,
			1610612741, 3221225473, 4294967291
		};

		//获取比prime大那一个素数
		size_t i = 0;
		for (i = 0; i < PRIMECOUNT; i++)
		{
			if (primeList[i] > prime)
				return primeList[i];
		}

		return primeList[i];
	}

	pair<iterator, bool> Insert(const T& data)
	{
		HashFunc hf;
		KeyOfT kot;

		iterator pos = Find(kot(data));
		if (pos != end())
		{
			return make_pair(pos, false);
		}

		//负载因子 == 1 扩容 -- 平均每个桶挂一个结点
		if (_tables.size() == _n)
		{
			//size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
			size_t newSize = GetNextPrime(_tables.size());

			if (newSize != _tables.size())
			{
				vector<Node*> newTable;
				newTable.resize(newSize, nullptr);

				//遍历旧表
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];

					//再对每个桶挨个遍历
					while (cur)
					{
						Node* next = cur->_next;
						size_t hashi = hf(kot(cur->_data)) % newSize;

						//转移到新的表中
						cur->_next = newTable[hashi];
						newTable[hashi] = cur;
						cur = next;
					}

					//将原表置空
					_tables[i] = nullptr;
				}
				newTable.swap(_tables);
			}
			
		}

		size_t hashi = hf(kot(data));
		hashi %= _tables.size();

		//头插到对应的桶即可
		Node* newnode = new Node(data);
		newnode->_next = _tables[hashi];
		_tables[hashi] = newnode;

		//有效数据加一
		_n++;

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

	iterator Find(const K& key)
	{
		if (_tables.size() == 0)
		{
			return iterator(nullptr, this);
		}

		KeyOfT kot;
		HashFunc hf;
		size_t hashi = hf(key);
		//size_t hashi = HashFunc()(key);

		hashi %= _tables.size();
		Node* cur = _tables[hashi];

		//找到指定的桶之后,顺着单链表挨个找
		while (cur)
		{
			if (kot(cur->_data) == key)
			{
				return iterator(cur, this);
			}

			cur = cur->_next;
		}

		//没找到返回空
		return iterator(nullptr, this);
	}

	bool Erase(const K& key)
	{
		if (_tables.size() == 0)
		{
			return false;
		}

		HashFunc hf;
		KeyOfT kot;
		size_t hashi = hf(key);
		hashi %= _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;

				return true;
			}

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

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

主要改造的地方就是上述所注意的地方:

  • 比较时需要调用各自的仿函数
  • 调用外部传的哈希函数

还有对扩容的二次思考:

研究表明:除留余数法,最好模一个素数

  • 通过查STL官方库我们也发现,其提供了一个取素数的函数
  • 所以我们也提供了一个,直接拷贝过来
    • 这样我们在扩容时就可以每次给素数个桶
    • 在扩容时加了一条判断语句是为了防止素数值太大,过分扩容容易直接把空间(堆)干崩了

(二)哈希桶的迭代器的模拟实现

1、begin()和end()的模拟实现

  • 以第一个桶中第一个不为空的结点为整个哈希桶的开始结点
  • 以空结点为哈希桶的结束结点

2、operator*和operator->及operator!=和operator==的模拟实现 

这两组和之前实现的一模一样,大家自行理解。

3、operator ++的模拟实现

注:

  • 这里要在哈希桶的类外面访问其私有成员
  • 我们要搞一个友元类
  • 迭代器类是哈希桶类的朋友
  • 这样就可以访问了

 

思路:

  • 判断一个桶中的数据是否遍历完
  • 如果所在的桶没有遍历完,在该桶中返回下一个结点指针
  • 如果所在的桶遍历完了,进入下一个桶
  • 判断下一个桶是否为空
  • 非空返回桶中第一个节点
  • 空的话就遍历一个桶
  • 后置++和之前一眼老套路,不赘述

注意:

unordered_map和unordered_set是不支持反向迭代器的,从底层结构我们也能很好的理解(单链表找不了前驱)所以不支持实现迭代器的operator- -

最后注意一点,我们需要知道哈希桶大小,所以不仅要传结点地址,还要传一个哈希桶,这样才能知道其大小,除此,由于哈希桶改造在后面,所以我们要在前面声明一下:

(三)迭代器和改造哈希桶的总代码

#include<vector>
#include<string>
#include<iostream>
using namespace std;

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

template<>
struct DefaultHash<string>
{
	size_t operator()(const string& key)
	{
		//BKDR
		size_t hash = 0;
		for (auto ch : key)
		{
			hash = hash * 131 + ch;
		}

		return hash;
	}
};

namespace 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 HashFunc>
	class HashTable;

	//哈希桶的迭代器
	template<class K, class T, class KeyOfT, class HashFunc>
	class __HTIterator
	{
		typedef HashNode<T> Node;
		typedef __HTIterator<K, T, KeyOfT, HashFunc> Self;
	public:
		Node* _node;

		__HTIterator() {};

		//编译器的原则是向上查找(定义必须在前面,否则必须先声明)
		HashTable<K, T, KeyOfT, HashFunc>* _pht;

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

		Self& operator++()
		{
			if (_node->_next)
			{
				_node = _node->_next;
			}
			else//当前桶已经走完了,要走下一个桶
			{
				KeyOfT kot;
				HashFunc hf;
				size_t hashi = hf(kot(_node->_data)) % _pht->_tables.size();
				hashi++;

				//找下一个不为空的桶 -- 访问到了哈希表中私有的成员(友元)
				for (; hashi < _pht->_tables.size(); hashi++)
				{
					if (_pht->_tables[hashi])
					{
						_node = _pht->_tables[hashi];
						break;
					}
				}

				//没有找到不为空的桶,用nullptr去做end标识
				if (hashi == _pht->_tables.size())
				{
					_node = nullptr;
				}
			}

			return *this;
		}

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

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

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

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

	//K --> 键值Key,T --> 数据
	//unordered_map ->HashTable<K, pair<K, V>, MapKeyOfT> _ht;
	//unordered_set ->HashTable<K, K, SetKeyOfT> _ht;
	template<class K, class T, class KeyOfT, class HashFunc>
	class HashTable
	{
		template<class K, class T, class KeyOfT, class HashFunc>
		friend class __HTIterator;

		typedef HashNode<T> Node;
	public:
		typedef __HTIterator<K, T, KeyOfT, HashFunc> iterator;

		iterator begin()
		{
			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);
		}

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

				_tables[i] = nullptr;
			}
		}

		size_t GetNextPrime(size_t prime)
		{
			const int PRIMECOUNT = 28;
			static const size_t primeList[PRIMECOUNT] =
			{
				53,         97,         193,       389,       769,
				1543,       3079,       6151,      12289,     24593,
				49157,      98317,      196613,    393241,    786433,
				1572869,    3145739,    6291469,   12582917,  25165843,
				50331653,   100663319,  201326611, 402653189, 805306457,
				1610612741, 3221225473, 4294967291
			};

			//获取比prime大那一个素数
			size_t i = 0;
			for (i = 0; i < PRIMECOUNT; i++)
			{
				if (primeList[i] > prime)
					return primeList[i];
			}

			return primeList[i];
		}

		pair<iterator, bool> Insert(const T& data)
		{
			HashFunc hf;
			KeyOfT kot;

			iterator pos = Find(kot(data));
			if (pos != end())
			{
				return make_pair(pos, false);
			}

			//负载因子 == 1 扩容 -- 平均每个桶挂一个结点
			if (_tables.size() == _n)
			{
				//size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				size_t newSize = GetNextPrime(_tables.size());

				if (newSize != _tables.size())
				{
					vector<Node*> newTable;
					newTable.resize(newSize, nullptr);

					//遍历旧表
					for (size_t i = 0; i < _tables.size(); i++)
					{
						Node* cur = _tables[i];

						//再对每个桶挨个遍历
						while (cur)
						{
							Node* next = cur->_next;
							size_t hashi = hf(kot(cur->_data)) % newSize;

							//转移到新的表中
							cur->_next = newTable[hashi];
							newTable[hashi] = cur;
							cur = next;
						}

						//将原表置空
						_tables[i] = nullptr;
					}
					newTable.swap(_tables);
				}

			}

			size_t hashi = hf(kot(data));
			hashi %= _tables.size();

			//头插到对应的桶即可
			Node* newnode = new Node(data);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;

			//有效数据加一
			_n++;

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

		iterator Find(const K& key)
		{
			if (_tables.size() == 0)
			{
				return iterator(nullptr, this);
			}

			KeyOfT kot;
			HashFunc hf;
			size_t hashi = hf(key);
			//size_t hashi = HashFunc()(key);

			hashi %= _tables.size();
			Node* cur = _tables[hashi];

			//找到指定的桶之后,顺着单链表挨个找
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return iterator(cur, this);
				}

				cur = cur->_next;
			}

			//没找到返回空
			return iterator(nullptr, this);
		}

		bool Erase(const K& key)
		{
			if (_tables.size() == 0)
			{
				return false;
			}

			HashFunc hf;
			KeyOfT kot;
			size_t hashi = hf(key);
			hashi %= _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;

					return true;
				}

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

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

(四)封装unordered_map和unordered_set

有了上面的哈希桶的改装,我们这里的对map和set的封装就显得很得心应手了。

unordered_map的封装:

#include "HashTable.h"

namespace zc
{
	template<class K, class V, class HashFunc = DefaultHash<K>>
	class unordered_map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename Bucket::HashTable<K, pair<K, V>, MapKeyOfT, HashFunc>::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);
		}

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

		bool erase(const K& key)
		{
			return _ht.Erase(key);
		}

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

	private:
		Bucket::HashTable<K, pair<K, V>, MapKeyOfT, HashFunc> _ht;
	};

	void test_map()
	{
		unordered_map<string, string> dict;
		dict.insert(make_pair("sort", "排序"));
		dict.insert(make_pair("left", "左边"));
		dict.insert(make_pair("left", "下面"));
		dict["string"];
		dict["left"] = "上面";
		dict["string"] = "字符串";

		unordered_map<string, string>::iterator it = dict.begin();
		while (it != dict.end())
		{
			cout << it->first << " " << it->second << endl;
			++it;
		}
		cout << endl;

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

}

这里unordered_map中的operator[ ]我们知道其原理之后,模拟实现就非常方便,直接调用插入函数,控制好参数和返回值即可。

对unordered_set的封装:

#include "HashTable.h"

#include "HashTable.h"

namespace zc
{
	template<class K, class HashFunc = DefaultHash<K>>
	class unordered_set
	{
		
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

	public:
		//2.48
		typedef typename Bucket::HashTable<K, K, SetKeyOfT, HashFunc>::iterator iterator;

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

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

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

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

		bool erase(const K& key)
		{
			return _ht.Erase(key);
		}
	private:
		Bucket::HashTable<K, K, SetKeyOfT, HashFunc> _ht;
	};

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

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

		int _year;
		int _month;
		int _day;
	};

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

			//cout << hash << endl;

			return hash;
		}
	};

	void test_set()
	{
		unordered_set<int> s;
		//set<int> s;
		s.insert(2);
		s.insert(3);
		s.insert(1);
		s.insert(2);
		s.insert(5);
		s.insert(12);

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

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

		unordered_set<Date, DateHash> sd;
		sd.insert(Date(2022, 3, 4));
		sd.insert(Date(2022, 4, 3));
	}
}

最后大家可以利用代码中给的测试函数进行测试!

感谢你的阅读!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值