15 unordered_map和unordered_set的使用以及用哈希桶模拟实现

文章目录

  • unordered_map,unordered_set,map和set的用法和区别
  • 用哈希桶模拟实现unordered_map和unordered_set
    • unordered_map的代码
    • unordered_set的代码
    • 哈希表和迭代器的代码


unordered_map,unordered_set,map和set的用法和区别

在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时的效率可达到 O ( logN ) ,即最差情况下需要比较红黑树的高度次,当树中的结点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同。

容器 底层数据结构 是否有序 实现版本 增删查改的效率 迭代器类型
unordered_map/unordered_set 哈希表/散列表 遍历无序 C++11 O ( 1 ) 单向迭代器
map/set 红黑树 遍历有序 C++98 O ( l o g N ) 双向迭代器

所以当处理数据量小时,map/set容器与unordered_map/unordered_set容器增删查改的效率差异不大。当处理数据量大时,map/set容器与unordered_map/unordered_set容器增删查改的效率相比,unordered系列容器的效率更高。

unordered_map,unordered_set在用法和接口上与map,set相同,当然他们也有允许键值冗余的unordered_multimap和unordered_multiset版本。
不过unordered_multimap和unordered_multiset没有反向迭代器,只有正向迭代器。

unordered_set当中常用的成员函数如下:

成员函数 功能
insert 插入指定元素
erase 删除指定元素
find 查找指定元素
size 获取容器中元素的个数
empty 判断容器是否为空
clear 清空容器
swap 交换两个容器中的数据
count 获取容器中指定元素值的元素个数
begin 获取容器中第一个元素的正向迭代器
end 获取容器中最后一个元素下一个位置的正向迭代器

unordered_map当中常用的成员函数如下:

成员函数 功能
insert 插入键值对
erase 删除指定key值的键值对
find 查找指定key值的键值对
size 获取容器中元素的个数
empty 判断容器是否为空
clear 清空容器
swap 交换两个容器中的数据
count 获取容器中指定key值的元素个数
begin 获取容器中第一个元素的正向迭代器
end 获取容器中最后一个元素下一个位置的正向迭代器

用哈希桶模拟实现unordered_map和unordered_set

模拟实现unordered_map和unordered_set的本质是调用哈希表的接口,这和用红黑树模拟实现set和map是一样的。
由于开散列(哈希桶)的性能比闭散列要高,因此使用哈希桶来进行模拟实现。

有几点需要注意:

  1. 哈希表模板参数的控制,第一个参数为K,第二个参数为T,所以需要实现仿函数获取第二个参数中的K。
  2. string类型无法取模,所以需要通过某种方法将字符串转换成整型后,才能代入哈希函数计算哈希地址。
template<class K>
struct Hash
{
	size_t operator()(const K& key) //返回键值key
	{
		return key;
	}
};
//string类型的特化
template<>
struct Hash<string>
{
	size_t operator()(const string& s) //BKDRHash算法
	{
		size_t value = 0;
		for (auto ch : s)
		{
			value = value * 131 + ch;
		}
		return value;
	}
};
  1. 哈希表的正向迭代器实际上就是对哈希结点指针进行了封装,但是由于在实现++运算符重载时,可能需要在哈希表中去寻找下一个非空哈希桶,因此每一个正向迭代器中都应该存储哈希表的地址。
//正向迭代器
template<class K, class T, class KeyOfT, class HashFunc = Hash<K>>
struct __HTIterator
{
	typedef HashNode<T> Node; //哈希结点的类型
	typedef HashTable<K, T, KeyOfT, HashFunc> HT; //哈希表的类型
	typedef __HTIterator<K, T, KeyOfT, HashFunc> Self; //正向迭代器的类型

	Node* _node; //结点指针
	HT* _pht; //哈希表的地址
	//构造函数
	__HTIterator(Node* node, HT* pht)
		:_node(node) //结点指针
		, _pht(pht) //哈希表地址
	{}
};
  1. 哈希表的迭代器类型是单向迭代器,没有反向迭代器,即没有实现–运算符的重载,若是想让哈希表支持双向遍历,可以考虑将哈希桶中存储的单链表结构换为双链表结构。

unordered_map的代码

#include"HashTable.h"

namespace hjl
{
	template<class K, class V>
	class unordered_map
	{
		//仿函数,用来获取K值
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv) 
			{
				return kv.first;
			}
		};
	public:
		typedef typename OpenHash::HashTable<K, pair<K, V>, MapKeyOfT>::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);
		}
		//赋值运算符重载
		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = insert(make_pair(key, V()));
			iterator it = ret.first;
			return it->second;
		}
		//删除函数
		void erase(const K& key)
		{
			_ht.Erase(key);
		}
		//查找函数
		iterator find(const K& key)
		{
			return _ht.Find(key);
		}
	private:
		OpenHash::HashTable<K, pair<K, V>, MapKeyOfT> _ht;
	};

}

unordered_set的代码

#include"HashTable.h"
namespace hjl
{
	template<class K>
	class unordered_set
	{
		//仿函数,用来获取K值
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename OpenHash::HashTable<K, K, SetKeyOfT>::iterator iterator;

		iterator begin()
		{
			return _ht.begin();
		}
		iterator end()
		{
			return _ht.end();
		}
		//插入函数
		pair<iterator, bool> insert(const K& key)
		{
			return _ht.Insert(key);
		}
		//删除函数
		void erase(const K& key)
		{
			_ht.Erase(key);
		}
		//查找函数
		iterator find(const K& key)
		{
			return _ht.Find(key);
		}
	private:
		OpenHash::HashTable<K, K, SetKeyOfT> _ht;
	};

}

哈希表和迭代器的代码

namespace OpenHash//开散列
{
	template<class T>
	struct HashNode
	{
		HashNode<T>* _next;
		T _data;
		HashNode(const T& data)
			:_next(nullptr)
			, _data(data)
		{}
	};
	template<class K>
	struct  Hash
	{
		size_t operator()(const K& key)
		{
			return key;
		}
	};
	template<>
	struct  Hash<string>
	{
		size_t operator()(const string& s)
		{
			size_t value = 0;
			for (auto ch : s)
			{
				value += ch;
				value *= 131;
			}
			return value;
		}
	};
	//前置声明,因为迭代器中用到了HashTable,HashTable中又用到了迭代器,
	//所以如果要先实现迭代器,必须要先声明HashTable
	template<class K, class T, class KeyOfT, class HashFunc>
	struct HashTable;
	//迭代器
	template<class K, class T, class KeyOfT, class HashFunc = Hash<K>>
	struct _HTIterator
	{
		typedef HashNode<T> Node;
		typedef _HTIterator<K, T, KeyOfT, HashFunc> Self;
		typedef HashTable<K, T, KeyOfT, HashFunc> HT;
		Node* _node; //当前结点的指针
		HT* _pht;//哈希表的地址地址,为了实现++重载
		_HTIterator(Node* node, HT* pht)
			:_node(node)
			,_pht(pht)
		{}
		Self& operator++()
		{
			//如果当前桶中还有数据,就在当前桶往后走
			//当前桶走完了,继续往后找下一个桶
			if (_node->_next)
			{
				_node = _node->_next;
			}
			else//该结点是当前哈希桶中的最后一个结点
			{
				size_t index = HashFunc()( KeyOfT()(_node->_data)) % _pht->_table.size();
				//开始找后面的桶
				++index;
				while (index<_pht->_table.size())
				{
					//当前哈希桶非空,直接返回头结点
					if (_pht->_table[index])
					{
						_node = _pht->_table[index];
						return *this;
					}
					//为空则继续找后面的桶
					else
					{
						++index;
					}
				}
				_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;
		}

	};


	template<class K, class T, class KeyOfT,class HashFunc = Hash<K>>
	struct HashTable
	{
	public:
		template<class K, class T, class KeyOfT, class HashFunc>
		friend struct _HTIterator;
		typedef HashNode<T> Node;
		typedef _HTIterator<K, T, KeyOfT, HashFunc> iterator;
		HashTable() = default;//显示指定生成默认构造
		HashTable(const HashTable& ht)
		{
			//1、将哈希表的大小调整为ht._table的大小
			_n = ht._n;
			_table.resize(ht._table.size());
			//2、将ht._table每个桶当中的结点一个个拷贝到自己的哈希表中
			for (size_t i = 0; i < ht._table.size(); i++)
			{
				Node* cur = ht._table[i];
				while (cur)
				{
					Node* copy = new Node(cur->_data);
					copy->_next = _table[i];
					_table[i] = copy;
					//取下一个待拷贝结点
					cur = cur->_next;
				}
			}
		}
		HashTable& operator=(HashTable ht)
		{
			_table.swap(ht._table);
			swap(_n, ht._n);
			//返回哈希表的引用,从而实现连续赋值
			return *this;
		}
		~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;
			}
		}
		iterator begin()
		{
			size_t i = 0;
			//找到第一个非空哈希桶
			while (i < _table.size())
			{
				if (_table[i])
				{
					//返回该哈希桶中的第一个结点的正向迭代器
					return iterator(_table[i], this);
				}
				++i;
			}
			return end();
		}
		iterator end()
		{
			return iterator(nullptr, this);
		}
		iterator Find(const K& key)
		{
			HashFunc hf;
			KeyOfT kot;
			if (_table.size() == 0) return end();
			size_t index = hf(key) % _table.size();
			Node* cur = _table[index];
			while (cur)
			{
				if (kot(cur->_data)== key)
				{
					return iterator(cur,this);
				}
				else
				{
					cur = cur->_next;
				}
			}
			return  end();
		}

		//获取本次增容后哈希表的大小
		size_t GetNextPrime(size_t prime)
		{
			const int PRIMECOUNT = 28;
			//素数序列
			const size_t primeList[PRIMECOUNT] =
			{
				53ul, 97ul, 193ul, 389ul, 769ul,
				1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
				49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
				1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
				50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
				1610612741ul, 3221225473ul, 4294967291ul
			};
			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;
			auto ret=Find(kot(data));
			if (ret != end())
			{
				return make_pair(ret, false);
			}
				//负载因子到1时进行增容
			if (_n == _table.size())
			{
					vector<Node*>newtable;
					//size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;
					//newtable.resize(newSize);

					newtable.resize(GetNextPrime(_table.size()));

					//遍历旧表中的节点,重新计算映射位置,挂到新表中
					for (size_t i = 0; i < _table.size(); ++i)
					{
						if (_table[i])
						{
							Node* cur = _table[i];
							while (cur)
							{
								//记录原来cur后面的节点
								Node* next = cur->_next;
								size_t index = hf(kot(cur->_data)) % newtable.size();
								//头插
								cur->_next = _table[index];
								_table[index] = cur;
								cur = next;
							}
							_table[i] = nullptr;
						}
					}
				_table.swap(newtable);
			}
			//将键值对插入哈希表
			size_t index = hf(kot(data)) % _table.size();
			Node* newnode = new Node(data);
			newnode->_next = _table[index];
			_table[index] = newnode;
			++_n;
			return make_pair(iterator(newnode, this), true);
		}
		
		bool Erase(const K& key)
		{
			HashFunc hf;
			size_t index = hf(key) % _table.size();
			Node* prev = nullptr;
			Node* cur = _table[index];
			while (cur)
			{
				//找到了待删除结点,则删除该结点
				if (kot(cur->_data) == key)
				{
					if (_table[index] == cur)
					{
						_table[index] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					--_n;
					delete cur;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			//要删的节点不存在
			return false;
		}
	private:
		vector<Node*> _table;
		size_t _n = 0;//有效数据的个数
	};
}

资料参考:
STL详解(十三)—— 用一个哈希表同时封装出unordered_map和unordered_set
哈希表底层探索

你可能感兴趣的:(C++,哈希算法,数据结构,算法)