【C++】unordered_map & unordered_set 底层刨析

文章目录

  • [1. 哈希表的改造](#1. 哈希表的改造)
  • [2. unordered_map](#2. unordered_map)
  • [3. unordered_set](#3. unordered_set)


C++ STL 库中,unordered_map 和 unordered_set 容器的底层为哈希表,本文将简单模拟哈希表(哈希桶),unordered_map 和 unordered_set 只需封装哈希表的接口即可实现。

1. 哈希表的改造

  1. 模板参数列表的改造

    • K:关键码类型
    • T:不同容器 T 的类型不同,如果是 unordered_map,T 代表一个键值对,如果是 unordered_set,T 为 K
    • KeyOfT:从 T 中获取 Key
    • Hash:哈希函数仿函数对象类型,哈希函数使用除留余数法,需要将 Key 转换为整型数字才能取模
    cpp 复制代码
    template<class K, class T, class KeyOfT, class Hash>
    class HashTable;
  2. 增加迭代器操作

    cpp 复制代码
    // 为了实现简单,在哈希桶的迭代器类中需要用到HashTable本身
    template<class K, class T, class KeyOfT, class Hash>
    class HashTable;
    
    // 注意:因为哈希桶在底层是单链表结构,所以哈希桶的迭代器不需要--操作
    template<class K, class T, class KeyOfT, class Hash>
    struct __HTIterator
    {
    	typedef HashNode<T> Node;
    	typedef HashTable<K, T, KeyOfT, Hash> HT;
    	typedef __HTIterator<K, T, KeyOfT, Hash> Self;
    
    	Node* _node;	// 当前迭代器关联的节点
    	HT* _ht;		// 哈希桶 - 主要是为了找下一个空桶时候方便
    
    	__HTIterator(Node* node, HT* ht)
    		: _node(node)
    		, _ht(ht)
    	{}
    
    	T& operator*()
    	{
    		return _node->_data;
    	}
    
    	Self& operator++()
    	{
    		if (_node->_next)
    		{
    			// 当前桶还是节点
    			_node = _node->_next;
    		}
    		else
    		{
    			// 当前桶走完了,找下一个桶
    			KeyOfT kot;
    			Hash hs;
    			size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();
    			
    			// 找下一个桶
    			hashi++;
    			while (hashi < _ht->_tables.size())
    			{
    				if (_ht->_tables[hashi])
    				{
    					_node = _ht->_tables[hashi];
    					break;
    				}
    				hashi++;
    			}
    
    			// 后面没有桶了
    			if (hashi == _ht->_tables.size())
    			{
    				_node = nullptr;
    			}
    		}
    		return *this;
    	}
    
    	bool operator!=(const Self& s)
    	{
    		return _node != s._node;
    	}
    };
  3. 增加通过 T 获取 key 操作

    cpp 复制代码
    template<class K, class T, class KeyOfT, class Hash>
    class HashTable
    {
    	template<class K, class T, class KeyOfT, class Hash>
    	friend struct __HTIterator;
    
    	typedef HashNode<T> Node;
    
    public:
    	typedef __HTIterator<K, T, KeyOfT, Hash> iterator;
    
    	iterator begin()
    	{
    		for (size_t i = 0; i < _tables.size(); i++)
    		{
    			// 找到第一个桶的第一个节点
    			if (_tables[i])
    			{
    				return iterator(_tables[i], this);
    			}
    		}
    		return end();
    	}
    
    	iterator end()
    	{
    		return iterator(nullptr, this);
    	}
    
    	HashTable()
    	{
    		_tables.resize(10, nullptr);
    		_n = 0;
    	}
    
    	~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;
    		}
    	}
    
    	bool Insert(const T& data)
    	{
    		KeyOfT kot;
    
    		if (Find(kot(data)))
    			return false;
    
    		Hash hs;
    
    		// 负载因子到1就扩容
    		if (_n == _tables.size())
    		{
    			vector<Node*> newTables(_tables.size() * 2, nullptr);
    			for (size_t i = 0; i < _tables.size(); i++)
    			{
    				// 取出旧表中节点,重新计算挂到新表桶中
    				Node* cur = _tables[i];
    				while (cur)
    				{
    					Node* next = cur->_next;
    
    					// 头插到新表
    					size_t hashi = hs(kot(cur->_data)) % newTables.size();
    					cur->_next = newTables[hashi];
    					newTables[hashi] = cur;
    
    					cur = next;
    				}
    				_tables[i] = nullptr;
    			}
    			_tables.swap(newTables);
    		}
    
    		size_t hashi = hs(kot(data)) % _tables.size();
    		Node* newnode = new Node(data);
    
    		// 头插
    		newnode->_next = _tables[hashi];
    		_tables[hashi] = newnode;
    
    		++_n;
    		return true;
    	}
    
    	Node* Find(const K& key)
    	{
    		KeyOfT kot;
    		Hash hs;
    		size_t hashi = hs(key) % _tables.size();
    		Node* cur = _tables[hashi];
    		while (cur)
    		{
    			if (kot(cur->_data) == key)
    			{
    				return cur;
    			}
    			cur = cur->_next;
    		}
    		return nullptr;
    	}
    
    	bool Erase(const K& key)
    	{
    		KeyOfT kot;
    		Hash hs;
    		size_t hashi = hs(key) % _tables.size();
    		Node* prev = nullptr;
    		Node* cur = _tables[hashi];
    		while (cur)
    		{
    			if (kot(cur->_data) == key)
    			{
    				// 删除
    				if (prev)
    				{
    					prev->_next = cur->_next;
    				}
    				else
    				{
    					_tables[hashi] = cur->_next;
    				}
    
    				delete cur;
    
    				--_n;
    				return true;
    			}
    			prev = cur;
    			cur = cur->_next;
    		}
    		return false;
    	}
    
    private:
    	vector<Node*> _tables;	// 指针数组
    	size_t _n;
    };

2. unordered_map

  • unordered_map 中存储的是 pair<K, V> 的键值对,K 为 key 的类型,V 为 value 的类型,Hash 为哈希函数类型
  • unordered_map 在实现时,只需将 HashTable 中的接口重新封装即可
cpp 复制代码
template<class K, class V, class Hash = HashFunc<K>>
class unordered_map
{
	// 通过T获取key的操作
	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;
		
	iterator begin()
	{
		return _ht.begin();
	}

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

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

private:
	hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
};

3. unordered_set

  • unordered_set 的实现类似于 unordered_map
cpp 复制代码
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;

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

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

	bool insert(const K& key)
	{
		return _ht.Insert(key);
	}

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

END

相关推荐
A懿轩A9 分钟前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组
机器视觉知识推荐、就业指导14 分钟前
C++设计模式:享元模式 (附文字处理系统中的字符对象案例)
c++
半盏茶香14 分钟前
在21世纪的我用C语言探寻世界本质 ——编译和链接(编译环境和运行环境)
c语言·开发语言·c++·算法
Evand J1 小时前
LOS/NLOS环境建模与三维TOA定位,MATLAB仿真程序,可自定义锚点数量和轨迹点长度
开发语言·matlab
LucianaiB1 小时前
探索CSDN博客数据:使用Python爬虫技术
开发语言·爬虫·python
️南城丶北离1 小时前
[数据结构]图——C++描述
数据结构··最小生成树·最短路径·aov网络·aoe网络
Ronin3051 小时前
11.vector的介绍及模拟实现
开发语言·c++
✿ ༺ ོIT技术༻1 小时前
C++11:新特性&右值引用&移动语义
linux·数据结构·c++
字节高级特工1 小时前
【C++】深入剖析默认成员函数3:拷贝构造函数
c语言·c++
计算机学长大白2 小时前
C中设计不允许继承的类的实现方法是什么?
c语言·开发语言