【C++进阶】哈希表封装unordered_map和unordered_set

🎬 博主名称键盘敲碎了雾霭
🔥 个人专栏 : 《C语言》《数据结构》 《C++》 《Matlab》 《Python》

⛺️指尖敲代码,雾霭皆可破


文章目录

  • 一、源码分析
    • [1.1 stl_hash_set](#1.1 stl_hash_set)
    • [1.2 stl_hash_map](#1.2 stl_hash_map)
    • [1.3 stl_hashtable.h](#1.3 stl_hashtable.h)
    • [1.4 总体思路](#1.4 总体思路)
  • 二、模拟实现
    • [2.1 实现哈希表](#2.1 实现哈希表)
    • [2.2 iterator的实现](#2.2 iterator的实现)
    • [2.3 map里\[\]的实现](#2.3 map里[]的实现)
    • [2.4 易错点](#2.4 易错点)
      • [2.4.1 **前置声明**](#2.4.1 前置声明)
      • [2.4.2 友元声明](#2.4.2 友元声明)
      • [2.4.3 const范围](#2.4.3 const范围)
  • 三、完整代码
    • [3.1 Unorderedset.h](#3.1 Unorderedset.h)
    • [3.2 Unorderedmap.h](#3.2 Unorderedmap.h)
    • [3.3 HashTable.h](#3.3 HashTable.h)
  • 文章结语

一、源码分析

SGI-STL30实现了哈希表,容器的名字是hash_map和hash_set,他是作为非标准的容器出现的,非标准是指非C++标准规定必须实现的,源代码在hash_map/hash_set/stl_hash_set/stl_hashtable.h

1.1 stl_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(); }
};

1.2 stl_hash_map

cpp 复制代码
// 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;
};

1.3 stl_hashtable.h

cpp 复制代码
// 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;
};

1.4 总体思路

通过源码可以看到,结构上hash_map和hash_set跟map和set的完全类似,复用同一个hashtable实现key和key/value结构,hash_set传给hash_table的是两个key,hash_map传给hash_table的是pair<const key,value>

这里跟实现map与set类似

不懂的小伙伴看这篇【C++进阶】红黑树模拟实现mymap和myset

二、模拟实现

2.1 实现哈希表

参考源码框架,unordered_map和unordered_set复用之前我们实现的哈希表。

之前已经实现过了,不会的可以看这篇

【C++进阶】哈希表实现(哈希函数 + 冲突处理 + 完整代码)

因为HashTable实现了泛型不知道T参数导致是K,还是pair<K,V>,那么insert内部进行插入时要用K对象转换成整形取模和K比较相等,因为pair的value不参与计算取模,且默认支持的是key和value一起比较相等,我们需要时的任何时候只需要比较K对象,所以我们在unordered_map和unordered_set层分别实现一个MapKeyOfT和SetKeyOfT的仿函数传给HashTable的KeyOfT,然后HashTable中通过KeyOfT仿函数取出T类型对象中的K对象,再转換成整形取模和K比较相等

2.2 iterator的实现

  • iterator实现的大框架跟list的iterator思路是一致的,用一个类型封装结点的指针,再通过重载运算符实现,迭代器像指针一样访问的行为,要注意的是哈希表的迭代器是单向迭代器
  • 这里的难点是operator++的实现。iterator中有一个指向结点的指针,如果当前桶下面还有结点,则结点的指针指向下一个结点即可。如果当前桶走完了,则需要想办法计算找到下一个桶。这里的难点是反而是结构设计的问题,需要节点指针和表指针两个变量,这样当前桶走完了,要计算下一个桶就相对容易多了,用key值计算出当前桶位置,依次往后找下一个不为空的桶即可。
  • begin()返回第一个桶中第一个节点指针构造的迭代器,这里end()返回迭代器可以用空表示。
  • unordered_set的iterator也不支持修改,我们把unordered_set的第二个模板参数改成const K即可
cpp 复制代码
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> table;
	typedef HTIterator<K, T, Ref, Ptr, KeyofT, Hash> Self;
	Node* _node;
	const table* _tb;
	HTIterator(Node* node, const table* tb)//要const修饰
		:_node(node)
		, _tb(tb)
	{

	}
	T& operator*()
	{
		return _node->_data;
	}
	T* operator->()
	{
		return &_node->_data;
	}
	bool operator==(const Self& tmp)
	{
		return _node == tmp._node;
	}
	bool operator!=(const Self& tmp)
	{
		return _node != tmp._node;
	}
	Self& operator++()
	{
		KeyofT kot;
		Hash hst;
		if (_node->_next)
		{
			_node = _node->_next;
		}
		else
		{
			size_t hash = hst(kot(_node->_data)) % _tb->_table.size();//私有成员
			++hash;
			while (hash < _tb->_table.size())
			{
				_node = _tb->_table[hash];
				if (_node)
				{
					break;
				}
				else
				{
					hash++;
				}
			}
			if (hash == _tb->_table.size())
			{
				_node = nullptr;
			}
		}
		return *this;
	}
};

2.3 map里\[\]的实现

核心是修改修改HashTable中的insert返回值为pair<ITERATOR,bool> ,这样既能支持修改和插入

cpp 复制代码
V& operator[](const K& Key)
	{
		pair<iterator, bool> ret = _table.Insert({ Key,V() });
		return ret.first->second;
	}

2.4 易错点

2.4.1 前置声明

由于类HashTable和类HTIterator相互依赖

需要加类模板的前置声明

cpp 复制代码
template<class K, class T, class KeyofT, class Hash >
class HashTable;

2.4.2 友元声明

在迭代器里实现++时,出现了使用类HashTable的私有成员需加上友元声明

cpp 复制代码
template<class K, class T, class Ref, class Ptr, class KeyofT, class Hash>
friend struct HTIterator;

2.4.3 const范围

在实现const迭代器,传递的是const修饰的表指针,需要更改

cpp 复制代码
		HTIterator(Node* node, const table* tb)//要const修饰
			:_node(node)
			, _tb(tb)
		{

		}

三、完整代码

3.1 Unorderedset.h

cpp 复制代码
#pragma once
#include"HashTable.h"
namespace KeyBreak
{
	template<class K, class Hash = HashFuc<K>>
	class Unorderedset
	{
		struct KeyofSet
		{
			const K& operator()(const K& Key)
			{
				return Key;
			}
		};
	public:
		typedef typename hash_bucket::HashTable<K,const K,KeyofSet,Hash>::ITERATOR iterator;
		typedef typename hash_bucket::HashTable<K,const K,KeyofSet,Hash>::CONSTITERATOR const_iterator;
		pair<iterator, bool> insert(const K& Key)
		{
			return _table.Insert(Key);
		}
		iterator begin()
		{
			return _table.begin();
		}
		iterator end()
		{
			return _table.end();
		}
		const_iterator begin()const
		{
			return _table.begin();
		}
		const_iterator end()const
		{
			return _table.end();
		}

	private:
		hash_bucket::HashTable<K, const K, KeyofSet,Hash> _table;
	};

	void test01()
	{
		int a[] = { 1,2,3,4,5,6,7 };
		Unorderedset<int> T;
		for (auto e : a)
		{
			T.insert({ e});
		}
		Unorderedset<int>::iterator it = T.begin();
		while (it != T.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
		for (auto e : T)
		{
			cout << e << " ";
		}
		const Unorderedset<int> T2(T);
		for (auto e : T2)
		{
			cout << e << " ";
		}
	}
}

3.2 Unorderedmap.h

cpp 复制代码
#pragma once
#include"HashTable.h"
namespace KeyBreak
{
	template<class K,class V, class Hash = HashFuc<K>>
	class Unorderedmap
	{
		struct KeyofMap
		{
			const K& operator()(const pair<K,V>& KV)
			{
				return KV.first;
			}
		};
	public:
		typedef typename hash_bucket::HashTable<K, pair<const K, V>, KeyofMap,Hash>::ITERATOR iterator;
		typedef typename hash_bucket::HashTable<K, pair<const K, V>, KeyofMap,Hash>::CONSTITERATOR const_iterator;

		pair<iterator, bool> insert(const pair<K,V>&KV)
		{
			return _table.Insert(KV);
		}
		iterator begin()
		{
			return _table.begin();
		}
		iterator end()
		{
			return _table.end();
		}
		const_iterator begin()const
		{
			return _table.begin();
		}
		const_iterator end()const
		{
			return _table.end();
		}
		V& operator[](const K& Key)
		{
			pair<iterator, bool> ret = _table.Insert({ Key,V() });
			return ret.first->second;
		}
	private:
		hash_bucket::HashTable<K, pair<const K,V>, KeyofMap,Hash> _table;
	};

	void test02()
	{
		Unorderedmap<string, string> dict;
		dict.insert({ "sort", "排序" });
		dict.insert({ "字符串", "string" });

		dict.insert({ "sort", "排序" });
		dict.insert({ "left", "左边" });
		dict.insert({ "right", "右边" });

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

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

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

3.3 HashTable.h

cpp 复制代码
#pragma once
#include<vector>
#include<iostream>
using namespace std;
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;
}
template<class K>
struct HashFuc
{
	size_t operator()(const K& Key)
	{
		return (size_t)Key;
	}
};
template<>
struct HashFuc<string>
{
	size_t operator()(const string& s)
	{
		size_t hash0 = 0;
		for (auto e : s)
		{
			hash0 += e;
			hash0 *= 131;
		}
		return hash0;
	}
};
namespace hash_address
{
	enum State
	{
		EMPTY,
		DELETE,
		EXIT
	};
	template<class K, class V>
	struct HashNode
	{
		pair<K, V> _kv;
		State _state;
		HashNode()
			:_state(EMPTY)
		{

		}
	};
	template<class K, class V, class Hash = HashFuc<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;
	public:
		HashTable()
			:_table(__stl_next_prime(0))
			, _n(0)
		{

		}
		bool Insert(const pair<K, V>& KV)
		{
			Hash hast;
			if(Find(KV.first))
			{
				return false;
			}
			if (_n / (double)_table.size() >= 0.7)
			{
				HashTable<K,V,Hash> newtable;
				newtable._table.resize(__stl_next_prime(_table.size()+1));
				for (auto e : _table)
				{
					newtable.Insert(e._kv);
				}
				_table.swap(newtable._table);
			}
			size_t hash0 = hast(KV.first) % (_table.size());
			size_t hashi = hash0;
			int i = 1;
			while (_table[hashi]._state == EXIT)
			{
				hashi = (hash0 + i) % (_table.size());
				++i;
			}
			_table[hashi]._kv.first = KV.first;
			_table[hashi]._kv.second = KV.second;
			_table[hashi]._state = EXIT;
			_n++;
			return true;
		}
		Node* Find(const K& Key)
		{
			Hash hast;
			size_t hash0 = hast(Key) % (_table.size());
			size_t hashi = hash0;
			int i = 0;
			while (_table[hashi]._state == EXIT)
			{
				if (_table[hashi]._state != EMPTY && _table[hashi]._kv.first == Key)
				{
					return &_table[hashi];
				}
				hashi = (hash0 + i) % (_table.size());
				++i;
			}
			return nullptr;
		}
		bool Erase(const K& Key)
		{
			Node* cur = Find(Key);
			if (cur)
			{
				cur->_state = DELETE;
				_n--;
				return true;
			}
			else
			{
				return false;
			}
		}
	private:
		vector<Node> _table;
		size_t _n;
	};
}

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> table;
		typedef HTIterator<K, T, Ref, Ptr, KeyofT, Hash> Self;
		Node* _node;
		const table* _tb;
		HTIterator(Node* node, const table* tb)//要const修饰
			:_node(node)
			, _tb(tb)
		{

		}
		T& operator*()
		{
			return _node->_data;
		}
		T* operator->()
		{
			return &_node->_data;
		}
		bool operator==(const Self& tmp)
		{
			return _node == tmp._node;
		}
		bool operator!=(const Self& tmp)
		{
			return _node != tmp._node;
		}
		Self& operator++()
		{
			KeyofT kot;
			Hash hst;
			if (_node->_next)
			{
				_node = _node->_next;
			}
			else
			{
				size_t hash = hst(kot(_node->_data)) % _tb->_table.size();//私有成员
				++hash;
				while (hash < _tb->_table.size())
				{
					_node = _tb->_table[hash];
					if (_node)
					{
						break;
					}
					else
					{
						hash++;
					}
				}
				if (hash == _tb->_table.size())
				{
					_node = nullptr;
				}
			}
			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()
		{
			if (_n == 0)
			{
				return end();
			}
			for (size_t i = 0; i < _table.size(); i++)
			{
				if (_table[i])
				{
					return { _table[i],this };
				}
			}
			return end();
		}
		ITERATOR end()
		{
			return { nullptr,this };
		}
		CONSTITERATOR begin()const
		{
			if (_n == 0)
			{
				return end();
			}
			for (size_t i = 0; i < _table.size(); i++)
			{
				if (_table[i])
				{
					return { _table[i],this };
				}
			}
			return end();
		}
		CONSTITERATOR end()const
		{
			return { nullptr,this };
		}

		HashTable()
			:_table(11)
			, _n(0)
		{

		}
		pair<ITERATOR,bool> Insert(const T& data)
		{
			KeyofT kot;
			Hash hst;
			ITERATOR ret = Find(kot(data));
			if (ret!=end())
			{
				return { ret,false};
			}
			if (_n / (double)_table.size() == 1)
			{
				vector<Node*> newtable(2*_table.size());
				for (size_t i = 0; i < _table.size(); i++)
				{
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;
						size_t hash0 = hst(kot(cur->_data)) % newtable.size();
						cur->_next = newtable[hash0];
						newtable[hash0] = cur;
						cur = next;
					}
					_table[i] = nullptr;
				}
				_table.swap(newtable);
			}
			size_t hash0 = hst(kot(data)) % _table.size();
			Node* newnode = new Node(data);
			newnode->_next = _table[hash0];
			_table[hash0] = newnode;
			_n++;
			return { ITERATOR(newnode,this),true};
		}
		ITERATOR Find(const K& Key)
		{
			KeyofT kot;
			Hash hst;
			size_t hash0 = hst(Key) % _table.size();
			Node* cur = _table[hash0];
			while (cur)
			{
				if (kot(cur->_data) == Key)
				{
					return ITERATOR(cur,this);
				}
				cur = cur->_next;
			}
			return ITERATOR(nullptr,this);
		}
		bool Erase(const K& Key)
		{
			KeyofT kot;
			Hash hst;
			size_t hash0 = hst(Key) % _table.size();
			Node* cur = _table[hash0];
			Node* pre = nullptr;
			while (cur)
			{
				if (kot(cur->_data) == Key)
				{
					if (pre == nullptr)
					{
						_table[hash0] = cur->_next;
					}
					else
					{
						pre->_next = cur->_next;
					}
					delete cur;
					_n--;
					return true;
				}
				else
				{
					pre = cur;
					cur = cur->_next;
				}
			}
			return false;
		}
	private:
		vector<Node*> _table;
		size_t _n;
	};
}

文章结语

感谢你读到这里~我是「键盘敲碎了雾霭」,愿这篇文字帮你敲开了技术里的小迷雾 💻

如果内容对你有一点点帮助,不妨给个暖心三连吧👇

👍 点赞 | ❤️ 收藏 | ⭐ 关注

(听说三连的小伙伴,代码一次编译过,bug绕着走~)

你的支持,就是我继续敲碎技术雾霭的最大动力 🚀

🐶 小彩蛋:

复制代码
      /^ ^\
     / 0 0 \
     V\ Y /V
      / - \
    /    |
   V__) ||

摸一摸毛茸茸的小狗,赶走所有疲惫和bug~我们下篇见 ✨

相关推荐
jelly酱1 小时前
Qt 坐标体系入门:从 GUI 概念到坐标实践
c++
c238561 小时前
C++ lambda 表达式详细介绍
开发语言·c++
无忧.芙桃1 小时前
debug实例与分析(一)
开发语言·c++·算法
alwaysrun1 小时前
C++之类型安全格式化format
c++·程序员·编程语言
邪修king1 小时前
C++ 哈希表超全详解:从底层实现到封装 myunordered_map/myunordered_set
c++·哈希算法·散列表
secret_to_me1 小时前
buildRoot编译rootfs实战
linux·c语言·c++·ubuntu·电脑·buildroot
凡人叶枫1 小时前
Effective C++ 条款01:视 C++ 为一个语言联邦
linux·开发语言·c++·effective c++·编程范式·语言联邦
QiLinkOS1 小时前
合肥气链科技有限公司本质总结
c++·科技·算法·gitee·开源
Yuk丶1 小时前
厌倦了假AI对话?本地 LLM 语音对话 + 口型同步系统 2.0(已开源!)
c++·人工智能·语言模型·开源·ue4·语音识别·游戏开发