【C++】模拟实现unordered_map和unordered_set

哈希表封装

  • 前言
  • 正式开始
    • 模型修改
    • Insert修改
    • 迭代器
      • 运算符重载
    • Find
    • operator[ ]

【C++】模拟实现unordered_map和unordered_set_第1张图片

前言

本篇以前一篇模拟实现哈希表为基础进行改造,如果没看过前一篇的先看一下:【C++】模拟实现哈希(闭散列和开散列两种方式)。

由于本篇代码基于上篇中开散列的代码进行改造,我就先把上篇中开散列实现的哈希表代码放到这里,各位可以不用看:

#pragma once

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

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


namespace FangZhang_OpenHash
{
	template<class K, class V>
	struct HashNode
	{
		HashNode(const pair<K, V>& kv = make_pair(K(), V()))
			:_kv(kv)
			, _next(nullptr)
		{}

		pair<K, V> _kv;
		HashNode<K, V>* _next;
	};

	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;

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

	public:

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

					_tables[i] = nullptr;
				}
			}
			
			_size = 0;
		}

		bool Insert(const pair<K, V>& kv)
		{
			// 去重
			if (Find(kv.first))
			{
				return false;
			}
			
			// 扩容
			// 此处扩容就不需要让负载因子为0.7了
			// 直接_size == _tables.size()就行
			// 因为不会出现占用其他数据位置的情况
			if (_size == _tables.size())
			{
				size_t newSize = __stl_next_prime(_size);
				// 这里可以利用前面开辟好的节点
				// 所以就不需要像闭散列那样再整一个哈希表了
				// 复用Insert的话开销比较大,还要重新给节点开空间
				// 所以直接给一个vector利用前面开的节点就好
				vector<Node*> newTables;
				newTables.resize(newSize);
				for (auto& node : _tables)
				{
					Node* cur = node;
					Hash hash;
					while (cur)
					{
						Node* next = cur->_next;

						size_t hashi = hash(cur->_kv.first) % newTables.size();
						cur->_next = newTables[hashi];
						newTables[hashi] = cur;

						cur = next;
					}

					node = nullptr;
				}

				_tables.swap(newTables);
			}

			// 真正插入
			Hash hash;
			size_t hashi = hash(kv.first) % _tables.size();

			// 头插
			Node* newNode = new Node(kv);
			newNode->_next = _tables[hashi];
			_tables[hashi] = newNode;

			++_size;

			return true;
		}

		Node* Find(const K& key)
		{
			if (_tables.size() == 0)
				return nullptr;

			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)
		{
			if (!Find(key))
			{
				return false;
			}

			Hash hash;
			size_t hashi = hash(key) % _tables.size();
			Node* cur = _tables[hashi];
			Node* prev = nullptr;
			while (cur && cur->_kv.first != key)
			{
				prev = cur;
				cur = cur->_next;
			}

			if (prev)
			{
				prev->_next = cur->_next;
			}
			else
			{
				_tables[hashi] = cur->_next;
			}
				
			delete cur;

			return true;
		}

		// 表的长度
		size_t TablesSize()
		{
			return _tables.size();
		}

		// 桶的个数
		size_t BucketNum()
		{
			size_t num = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				if (_tables[i])
				{
					++num;
				}
			}

			return num;
		}

		// 最长桶的长度
		size_t MaxBucketLenth()
		{
			size_t maxLen = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				size_t len = 0;
				Node* cur = _tables[i];
				while (cur)
				{
					++len;
					cur = cur->_next;
				}

				//if (len > 0)
					//printf("[%d]号桶长度:%d\n", i, len);

				if (len > maxLen)
				{
					maxLen = len;
				}
			}

			return maxLen;
		}

		size_t Size()
		{
			return _size;
		}

	private:
		inline size_t __stl_next_prime(size_t n)
		{
			static const size_t __stl_num_primes = 28;
			static const size_t __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
			};

			for (size_t i = 0; i < __stl_num_primes; ++i)
			{
				if (__stl_prime_list[i] > n)
				{
					return __stl_prime_list[i];
				}
			}

			return -1;
		}
	};
	
}

正式开始

首先要先搞两个头文件 UnOrderedMap.h 和 UnOrderedSet.h,这样的起名是为了不和库中的冲突。并将map和set写在命名空间FangZhang中。

模型修改

上面模拟实现中的哈希表是K/V模型的,我们这里要改成既能实现K/V模型的,又能实现K模型的。和我前面模拟实现map和set很相似。要将原来哈希表代码中的模版参数由KV改为T,并将所有的pair类型都换为模版T。
【C++】模拟实现unordered_map和unordered_set_第2张图片

【C++】模拟实现unordered_map和unordered_set_第3张图片

并在map和set中封装:
【C++】模拟实现unordered_map和unordered_set_第4张图片

【C++】模拟实现unordered_map和unordered_set_第5张图片

因为上面map和set中增加了第三个模版参数Hash并将其传给了哈希表,所以在哈希表中就不需要让第三个模版参数Hash给默认值HashFunc了。

Insert修改

Insert要改一下参数,改为data:
在这里插入图片描述
data表示当前哈希表中存放的数据,但是有一个问题,就是data的类型T我们不能确定,当模型是K时,有可能是int,但当模型是KV时,也有可能是pair

当代码改为如下时(粗略过一眼就行):
【C++】模拟实现unordered_map和unordered_set_第6张图片

其中hash函数中传参应该传的是key,而非data,当data为pair类型是key就是其first,当data为key时就直接是data就行,所以我们可以写一个仿函数,将仿函数写在map和set中,传过去data,返回来key就好。所以哈希表中又要加一个模版参数KeyOfT,用来提取出每个data的key。

【C++】模拟实现unordered_map和unordered_set_第7张图片

【C++】模拟实现unordered_map和unordered_set_第8张图片

【C++】模拟实现unordered_map和unordered_set_第9张图片
把Insert再改一下:
【C++】模拟实现unordered_map和unordered_set_第10张图片

在map和set中封装一下insert:
【C++】模拟实现unordered_map和unordered_set_第11张图片
【C++】模拟实现unordered_map和unordered_set_第12张图片

测试一下:
【C++】模拟实现unordered_map和unordered_set_第13张图片

【C++】模拟实现unordered_map和unordered_set_第14张图片

但上面没写迭代器,不能打印,我们下面实现一下迭代器。

迭代器

实现前要想清楚逻辑,当我们想遍历哈希表时,要挨着节点遍历,但中间有不想连的地方,如果其中一个非空链表走到头了,想要找下一个非空链表就得要借助其本身的哈希表,所以我们要在迭代器中给一个哈希表的指针,用来方便我们遍历。

【C++】模拟实现unordered_map和unordered_set_第15张图片

运算符重载

我们这里重载一下*、->、!=、++运算符,就能遍历了。

先说前三个:
【C++】模拟实现unordered_map和unordered_set_第16张图片

++:
【C++】模拟实现unordered_map和unordered_set_第17张图片

再在哈希表中写一下begin和end:

【C++】模拟实现unordered_map和unordered_set_第18张图片
并在map中封装一下:

【C++】模拟实现unordered_map和unordered_set_第19张图片

测试,发现出问题了,在哈希表中_ptb指针所指向的是整个哈希表结构体,其中解引用了_tables,而_tables在哈希表中是私有的,不可直接在迭代器中访问。
【C++】模拟实现unordered_map和unordered_set_第20张图片

而哈希表中也用到了迭代器,所以二者会出现相互调用的问题,此时我们就要用友元来解决这个问题了。迭代器中无法访问哈希表中的成员,所以我们要将迭代器设置为哈希表的友元。
【C++】模拟实现unordered_map和unordered_set_第21张图片

再测试:
【C++】模拟实现unordered_map和unordered_set_第22张图片

迭代器有了范围for也就能跑了:
【C++】模拟实现unordered_map和unordered_set_第23张图片

Find

改一下Find,返回值改为迭代器:
【C++】模拟实现unordered_map和unordered_set_第24张图片

重载一下迭代器的==,再改一下Erase前面的Find:
【C++】模拟实现unordered_map和unordered_set_第25张图片

我们用一下统计数量的测试:
【C++】模拟实现unordered_map和unordered_set_第26张图片
但是没有重载[ ]就比较麻烦,所以再来重载一下[ ]。只能在unordered_map中重载。

operator[ ]

如果看过我红黑树的模拟实现的话,这里就比较熟悉了。
我们直接复用一下insert就好。

但是要把insert的返回值改一下:
【C++】模拟实现unordered_map和unordered_set_第27张图片

再把unordered_map中的insert也改一下:
【C++】模拟实现unordered_map和unordered_set_第28张图片

再来重载[ ]:
【C++】模拟实现unordered_map和unordered_set_第29张图片

前面的博客中说过了,这里再解释一下:res.first就是对应插入节点的iterator,->返回_data的地址,但是编译器优化了,省略了一个->,所以就能直接得到_data,然后直接拿到second,也就是KV中的V。

测试:
【C++】模拟实现unordered_map和unordered_set_第30张图片

unordered_set就不搞了,比unordered_map还简单,感兴趣的老铁自己搞搞。这里模拟实现不是为了把所有的实现,主要是了解一下STL库的底层,更方便于我们学习。以后是绝对用不到这写模拟的。

到此结束。。。

你可能感兴趣的:(c++,哈希算法,开发语言,数据结构,算法)