在 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;
-
K e y Key Key 就是 u n o r d e r e d _ m a p unordered\_map unordered_map 底层关键字的类型。
-
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 的类型。
-
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 转成整形的仿函数传给第三个模板参数。
-
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 比较相等的仿函数传给第四个模板参数。
-
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 都围绕着支持值冗余有所差异:
-
i n s e r t insert insert 可以插入相同的值。
-
如果要查找的 x x x 有多个值, f i n d find find 会返回第一个迭代器。
-
c o u n t count count 会返回 x x x 的实际个数。
-
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 SGI−STL 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 SGI−STL 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 。
- 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;
};
}
- 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;
};
}
- 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,效率会高一些。