【STL】unordered_map

C C C++ 11 11 11 中, S T L STL STL 标准库引入了一个新的标准关联式容器 u n o r d e r e d _ m a p unordered\_map unordered_map无序集合)。功能和 s e t set set 类似,都用于存储唯一元素。但是其底层数据结构是哈希表,因此集合中的元素都是无序存储的,所以增删查的时间复杂度为 O ( 1 ) O(1) O(1),增删查的效率比 s e t set set 高。


一、unordered_map 的介绍

前面部分我们已经详细介绍了 m a p map map 容器,可以参考我的这篇博客:【STL】 m a p map map。由于 m a p map map u n o r d e r e d _ m a p unordered\_map unordered_map 这两个容器只是底层实现结构不同,其功能高度相似,基本上只要掌握 m a p map map 的用法, u n o r d e r e d _ m a p unordered\_map unordered_map 也就会用了。因此,和 m a p map map 相比只有一些性能和使用的差异,这里只介绍其差异部分。

在这里插入图片描述

u n o r d e r e d _ m a p unordered\_map unordered_map 的声明如下:

template < class Key,                                   // unordered_map::key_type
           class T,                                     // unordered_map::mapped_type
           class Hash = hash<Key>,                      // unordered_map::hasher
           class Pred = equal_to<Key>,                  // unordered_map::key_equal
           class Alloc = allocator< pair<const Key,T> >	// unordered_map::allocator_type
         > class unordered_map;
  1. K e y Key Key 就是 u n o r d e r e d _ m a p unordered\_map unordered_map 底层关键字的类型。

  2. T T T 就是 u n o r d e r e d _ m a p unordered\_map unordered_map 底层 v a l u e value value 的类型。

  3. u n o r d e r e d _ m a p unordered\_map unordered_map 默认要求 K e y Key Key 支持转换为整形,如果不支持或者有自己的需求可以自行实现支持将 K e y Key Key 转成整形的仿函数传给第三个模板参数。

  4. u n o r d e r e d _ m a p unordered\_map unordered_map 默认要求 K e y Key Key 支持比较相等,如果不支持或者有自己的需求可以自行实现支持将 K e y Key Key 比较相等的仿函数传给第四个模板参数。

  5. u n o r d e r e d _ m a p unordered\_map unordered_map 底层存储数据的内存是从空间配置器申请的,如果需要可以自己实现内存池,传给第五个模板参数。

注意:一般情况下,我们都不需要传后三个模板参数。

u n o r d e r e d _ m a p unordered\_map unordered_map 底层是用哈希桶实现,增删查平均效率是 O ( 1 ) O(1) O(1),迭代器遍历不再有序,为了跟 m a p map map 区分,所以取名 u n o r d e r e d _ m a p unordered\_map unordered_map无序集合)。


二、unordered_map 的使用(常用接口)

u n o r d e r e d _ m a p unordered\_map unordered_map 的底层结构是哈希表,因此不支持比较排序,所以细节上根据这一点和 m a p map map 有略微不同,其他都完全类似。这里只给出常用接口,更多详细信息可以自行查官方文档: u n o r d e r e d _ m a p unordered\_map unordered_map

1. 常见构造

构造 ( c o n s t r u c t o r ) (constructor) (constructor) 函数声明接口说明
u n o r d e r e d _ m a p ( ) unordered\_map() unordered_map()无参默认构造
u n o r d e r e d _ m a p ( c o n s t   u n o r d e r e d _ m a p &   u m p ) unordered\_map(const\ unordered\_map\&\ ump) unordered_map(const unordered_map& ump)拷贝构造
u n o r d e r e d _ m a p ( I n p u t I t e r a t o r   f i r s t , I n p u t I t e r a t o r   l a s t ) unordered\_map(InputIterator\ first, InputIterator\ last) unordered_map(InputIterator first,InputIterator last)使用迭代器区间构造
u n o r d e r e d _ m a p ( i n i t i a l i z e r _ l i s t < v a l u e _ t y p e > i l ) unordered\_map (initializer\_list<value\_type> il) unordered_map(initializer_list<value_type>il)使用 i n i t i a l i z e r initializer initializer 列表构造

2. iterator 的使用

i t e r a t o r iterator iterator 的使用接口说明
b e g i n ( ) begin() begin() + + + e n d ( ) end() end() i t e r a t o r iterator iterator
c b e g i n ( ) cbegin() cbegin() + + + c e n d ( ) cend() cend() c o n s t _ i t e r a t o r const\_iterator const_iterator

u n o r d e r e d _ m a p unordered\_map unordered_map 的迭代器是一个单向迭代器iterator -> a forward iterator to const value_type

在这里插入图片描述

3. 增删查

u n o r d e r e d _ m a p unordered\_map unordered_map 增删查接口说明
i n s e r t insert insert插入 v a l val val 数据
e r a s e erase erase删除 v a l val val 数据
f i n d find find查找 v a l val val,返回 v a l val val 位置的迭代器(没找到返回 e n d ( ) end() end()
c o u n t count count查找 v a l val val,返回 v a l val val 的个数

由于 u n o r d e r e d _ m a p unordered\_map unordered_map 不支持比较大小,且容器内元素是无序的,因此就没有 l o w e r _ b o u n d lower\_bound lower_bound u p p e r _ b o u n d upper\_bound upper_bound 接口了。

4. unordered_multimap

u n o r d e r e d _ m u l t i m a p unordered\_multimap unordered_multimap m u l t i m a p multimap multimap 的使用基本完全类似,都支持关键值( K e y Key Key)冗余

m u l t i m a p multimap multimap 完全类似, i n s e r t / f i n d / c o u n t / e r a s e insert/find/count/erase insert/find/count/erase 都围绕着支持值冗余有所差异:

  1. i n s e r t insert insert 可以插入相同的值

  2. 如果要查找的 x x x 有多个值, f i n d find find 会返回第一个迭代器

  3. c o u n t count count 会返回 x x x 的实际个数。

  4. e r a s e erase erase 指定值删除时,会删除所有的 x x x

并且 u n o r d e r e d _ m u l t i m a p unordered\_multimap unordered_multimap 也不支持 [   ] [\ ] [ ],因为支持 k e y key key 冗余, [   ] [\ ] [ ] 就只能支持插入了,不能支持修改。


三、unordered_map 的模拟实现

1. STL 中的 hash_map 源码

S G I − S T L   30 SGI-STL\ 30 SGISTL 30 版本是 C C C++ 11 11 11 之前的 S T L STL STL 版本,源代码中没有 u n o r d e r e d _ m a p unordered\_map unordered_map,因为这个容器是 C C C++ 11 11 11 之后才更新的。但是 S G I − S T L   30 SGI-STL\ 30 SGISTL 30 实现了哈希表 h a s h _ m a p hash\_map hash_map 作为非标准容器出现。关于哈希表这个数据结构的详细介绍,可以参考我的这篇博客:【数据结构】哈希表。

s t l _ h a s h _ m a p stl\_hash\_map stl_hash_map

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

2. unordered_map 的迭代器

u n o r d e r e d _ m a p unordered\_map unordered_map i t e r a t o r iterator iterator 不支持修改 k e y key key 但是可以修改 v a l u e value value,我们把 u n o r d e r e d _ m a p unordered\_map unordered_map 的第二个模板参数 p a i r pair pair 的第一个参数改成 c o n s t   K const\ K const K 即可。

typedef pair<const K, V> T;

typedef typename hashtable<K, T, MapKeyOfT, Hash>::iterator iterator;
typedef typename hashtable<K, T, MapKeyOfT, Hash>::const_iterator const_iterator;

hashtable<K, T, MapKeyOfT, Hash> _ht;

3. unordered_map 支持 operator[]

u n o r d e r e d _ m a p unordered\_map unordered_map 要支持 o p e r a t o r [   ] operator[\ ] operator[ ] 主要需要修改 i n s e r t insert insert 返回值支持,修改 h a s h t a b l e hashtable hashtable 中的 i n s e r t insert insert 返回值为 pair<iterator, bool> insert(const T& data) 即可。

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

4. 模拟实现 unordered_map

由于 u n o r d e r e d _ m a p unordered\_map unordered_map 属于 C C C++ S T L STL STL 容器模板类),因此, u n o r d e r e d _ m a p unordered\_map unordered_map 声明和定义都要写到一个文件中。又因为 u n o r d e r e d _ m a p unordered\_map unordered_map 的底层结构是哈希表,因此迭代器直接使用哈希表的迭代器即可。

总共分为三个文件:哈希表类直接使用我们之前泛型封装好的 h a s h t a b l e . h hashtable.h hashtable.h(底层数据结构); u n o r d e r e d _ m a p . h unordered\_map.h unordered_map.h 用来存放 u n o r d e r e d _ m a p unordered\_map unordered_map 的定义和实现; t e s t . c p p test.cpp test.cpp 用来测试。

注意:这里两个文件中的 u n o r d e r e d _ m a p unordered\_map unordered_map 都是自己定义的,为了和 s t d : : u n o r d e r e d _ m a p std::unordered\_map std::unordered_map 作区分,我们要单独创建一个命名空间,这里我用的是 n a m e s p a c e   y b c namespace\ ybc namespace ybc

  1. h a s h t a b l e . h hashtable.h hashtable.h
#pragma once
#include<iostream>
#include<vector>
#include<unordered_map>

using namespace std;

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

	// 特化
	template<>
	struct hash<string>
	{
		size_t operator() (const string& s)
		{
			size_t hash = 0;
			for (auto ch : s)
			{
				hash *= 131;
				hash += ch;
			}
			return hash;
		}
	};

	template<class T>
	struct hash_node
	{
		T _data;
		hash_node<T>* _next;

		hash_node(const T& data)
			:_data(data)
			, _next(nullptr)
		{}
	};

	// 前置声明
	template<class K, class T, class KeyOfT, class Hash>
	class hashtable;

	template<class K, class T, class Ptr, class Ref, class KeyOfT, class Hash>
	struct hash_iterator
	{
		typedef hash_node<T> node;
		typedef hashtable<K, T, KeyOfT, Hash> hashtable;
		typedef hash_iterator<K, T, Ptr, Ref, KeyOfT, Hash> self;

		node* _node;			// 当前结点的指针
		const hashtable* _ht;	// 哈希表对象的指针

		KeyOfT kot;
		Hash hash;

		hash_iterator(node* node, const hashtable* ht)
			:_node(node)
			, _ht(ht)
		{}

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

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

		self& operator ++ ()
		{
			if (_node->_next)
			{
				// 当前桶还有结点
				_node = _node->_next;
				return *this;
			}
			else
			{
				// 当前桶走完了,寻找下一个桶
				size_t hi = hash(kot(_node->_data)) % _ht->_t.size();
				while (++hi < _ht->_t.size())
				{
					if (_ht->_t[hi])
					{
						_node = _ht->_t[hi];
						return *this;
					}
				}
				_node = nullptr;
				return *this;
			}
		}

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

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

	template<class K, class T, class KeyOfT, class Hash>
	class hashtable
	{
		// 模板类的友元声明要带模板
		template<class K, class T, class Ptr, class Ref, class KeyOfT, class Hash>
		friend struct hash_iterator;

		typedef hash_node<T> node;

		KeyOfT kot;
		Hash hash;
	public:
		typedef hash_iterator<K, T, T*, T&, KeyOfT, Hash> iterator;
		typedef hash_iterator<K, T, const T*, const T&, KeyOfT, Hash> const_iterator;

		iterator begin()
		{
			if (_n == 0)
			{
				return end();
			}
			for (size_t i = 0; i < _t.size(); ++i)
			{
				node* cur = _t[i];
				if (cur)
				{
					return iterator(cur, this);
				}
			}
			return end();
		}

		iterator end()
		{
			return iterator(nullptr, this);
		}

		const_iterator cbegin() const
		{
			if (_n == 0)
			{
				return cend();
			}
			for (size_t i = 0; i < _t.size(); ++i)
			{
				node* cur = _t[i];
				if (cur)
				{
					return const_iterator(cur, this);
				}
			}
			return cend();
		}

		const_iterator cend() const
		{
			return const_iterator(nullptr, this);
		}

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

		hashtable()
			:_t(__stl_next_prime(0), nullptr)
			, _n(0)
		{}

		~hashtable()
		{
			for (size_t i = 0; i < _t.size(); ++i)
			{
				node* cur = _t[i];
				while (cur)
				{
					node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_t[i] = nullptr;
			}
		}

		pair<iterator, bool> insert(const T& data)
		{
			iterator it = find(kot(data));
			if (it != end())
			{
				return make_pair(it, false);
			}
			if (_n == _t.size())
			{
				vector<node*> newtable(__stl_next_prime((unsigned long)_t.size() + 1), nullptr);
				for (size_t i = 0; i < _t.size(); ++i)
				{
					node* cur = _t[i];
					while (cur)
					{
						node* next = cur->_next;
						size_t hi = hash(kot(cur->_data)) % newtable.size();

						cur->_next = newtable[hi];
						newtable[hi] = cur;
						cur = next;
					}
					_t[i] = nullptr;
				}
				_t.swap(newtable);
			}

			size_t hi = hash(kot(data)) % _t.size();
			node* newnode = new node(data);
			newnode->_next = _t[hi];
			_t[hi] = newnode;
			++_n;

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

		iterator find(const K& key)
		{
			size_t hi = hash(key) % _t.size();
			node* cur = _t[hi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return iterator(cur, this);
				}
				cur = cur->_next;
			}
			return end();
		}

		bool erase(const K& key)
		{
			size_t hi = hash(key) % _t.size();
			node* prev = nullptr;
			node* cur = _t[hi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					if (prev == nullptr)
					{
						_t[hi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					--_n;
					return true;
				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			return false;
		}
	private:
		vector<node*> _t;
		size_t _n;
	};
}
  1. u n o r d e r e d _ m a p . h unordered\_map.h unordered_map.h
#include"hashtable.h"

namespace ybc
{
	template<class K, class V, class Hash = hash<K>>
	class unordered_map
	{
		typedef pair<const K, V> T;
		
		struct MapKeyOfT
		{
			const K& operator () (const T& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename hashtable<K, T, MapKeyOfT, Hash>::iterator iterator;
		typedef typename hashtable<K, T, MapKeyOfT, Hash>::const_iterator const_iterator;
		
		iterator begin()
		{
			return _ht.begin();
		}

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

		const_iterator cbegin()
		{
			return _ht.cbegin();
		}

		const_iterator cend()
		{
			return _ht.cend();
		}

		pair<iterator, bool> insert(const T& 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:
		hashtable<K, T, MapKeyOfT, Hash> _ht;
	};
}
  1. t e s t . c p p test.cpp test.cpp
#include"unordered_map.h"

namespace ybc
{
	void test1()
	{
		unordered_map<int, int> ump;

		ump.insert({ 1,1 });
		ump.insert({ 2,4 });
		ump.insert({ 3,6 });
		ump.insert({ 4,8 });
		ump.insert({ 5,10 });

		ump[5]++;
		cout << ump[5] << endl << endl;

		auto it = ump.begin();
		cout << (*it).first << ":" << (*it).second << endl;

		while (it != ump.end())
		{
			it->second++;
			cout << it.operator->()->first << ":" << it->second << " ";
			++it;
		}
		cout << endl << endl;

		auto cit = ump.cbegin();
		cout << (*cit).first << ":" << (*cit).second << endl;
		while(cit != ump.cend())
		{
			cout << cit.operator->()->first << ":" << cit->second << " ";
			++cit;
		}
		cout << endl << endl;

		for (const auto& i : ump)
		{
			cout << i.first << ":" << i.second << " ";
		}
		cout << endl;
	}

	void test2()
	{
		unordered_map<string, string> dict;

		dict.insert({ "sort", "排序" });
		dict.insert({ "left", "左边" });
		dict.insert({ "right", "右边" });
		
		dict["left"] = "左边,剩余";
		dict["insert"] = "插入";
		dict["string"];

		unordered_map<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;
	}

	void test3()
	{
		unordered_map<int, int> ump;

		ump.insert({ 1,1 });
		ump.insert({ 2,4 });
		ump.insert({ 3,6 });
		ump.insert({ 4,8 });
		ump.insert({ 5,10 });

		if (ump.find(5) != ump.end())
			cout << "find" << endl;
		else
			cout << "no find" << endl;

		ump.erase(5);

		if (ump.find(5) != ump.end())
			cout << "find" << endl;
		else
			cout << "no find" << endl;

		for (auto i : ump)
		{
			cout << i.first << ":" << i.second << " ";
		}
		cout << endl;
	}
}

int main()
{
	//ybc::test1();

	//ybc::test2();

	ybc::test3();

	return 0;
}

总结

u n o r d e r e d _ m a p unordered\_map unordered_map 是在 C C C++ 11 11 11 之后才列入标准库的关联式容器,也就是说,要使用这个容器,必须支持 C C C++ 11 11 11-std=c++11)。

u n o r d e r e d _ m a p unordered\_map unordered_map 的使用方式和 m a p map map 完全类似,差别在于底层结构不同。其底层结构为哈希桶,因此增删查的效率为 O ( 1 ) O(1) O(1),但是其容器中的元素是无序存储的,也就是说,当需要大量增删查数据,并且不需要元素有序的时候可以用 u n o r d e r e d _ m a p unordered\_map unordered_map,效率会高一些。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值