赞
踩
unordered_set 是含有 Key 类型唯一对象集合的关联容器。搜索、插入和移除拥有平均常数时间复杂度。在内部,元素并不以任何特别顺序排序,而是组织进桶中。元素被放进哪个桶完全依赖其值的哈希。这允许对单独元素的快速访问,因为哈希一旦确定,就准确指代元素被放入的桶。不可修改容器元素(即使通过非 const 迭代器),因为修改可能更改元素的哈希,并破坏容器。
unordered_map 是关联容器,含有带唯一键的 “键-值” pair 。搜索、插入和元素移除拥有平均常数时间复杂度。元素在内部不以任何特定顺序排序,而是组织进桶中。元素放进哪个桶完全依赖于其键( Key) 的哈希。这允许对单独元素的快速访问,因为一旦计算哈希,则它准确指代元素所放进的桶。
读上面的两段话会显得非常抽象,但是,如果对模拟实现 Map、Set 有一定了解(不了解可以看一下这篇文章 Map、Set模拟实现 ),那么就能很快明白,只不过是 原本用红黑树作为底层数据结构来实现 Map、Set ,现在使用哈希(拉链法)作为底层数据结构来实现 unordered_map、unordered_set 。既然是使用的哈希,那么存储的数据就无法像红黑树一样具有严格的顺序,但是,换来的确是查找效率的极大提高,提高为 O(1) 的查找效率。
如下图,哈希桶本质上是一个 vector ,容器里面存放的是 HashNode 这个节点的指针,节点是一个结构体,结构体里面存储着下一个 HashNode 结构体的指针 和 当前节点的数据。
哈希桶的实现自然不必多说,不了解的话可以查看这篇文章:C++ 哈希。这里是使用拉链法解决哈希冲突。
如下,必然要定义一个 HashNode 结构体,HashFunc 是为了根据 Key 来计算出数据的哈希值,以便得知数据存放在哪一个桶里面,至于 正反向迭代器自然不必多说,必有的配置,最后就是 哈希桶了,其成员变量一个是 _tables ,另一个是用来表示 _tables 中的有效数据个数。
#pragma once #include<iostream> #include<vector> using namespace std; namespace HashBucket { template<class T> struct HashNode { HashNode<T>* _next; T _data; HashNode(const T& data) :_next(nullptr) , _data(data) {} }; template<class K> struct HashFunc { size_t operator()(const K& key) { return key; } }; // 模板的特化,如果有 string 类型的数据,优先使用特化的模板 template<> struct HashFunc<string> { size_t operator()(const string& s) { size_t hash = 0; for (auto& a : s) { hash += a; hash *= 31; } return hash; } }; // 前置声明,不需要默认参数 template<class K, class T, class KeyOfT, class Hash> class HashTable; template<class K,class T,class Ref,class Ptr,class KeyOfT,class Hash=HashFunc<K>> struct Iterator { public: typedef HashNode<T> Node; typedef Iterator<K, T, Ref, Ptr, KeyOfT, Hash> self; typedef HashTable<K, T, KeyOfT, Hash> HT; public: Iterator(Node* node = nullptr, HT* tables= nullptr) :_node(node) ,_ht(tables) {} Iterator(const Iterator<K,T,T&,T*,KeyOfT,Hash>& it) :_node(it._node) ,_ht(it._ht) {} Node* _node; HT* _ht; }; template<class K,class T,class Ref,class Ptr,class KeyOfT,class Hash=HashFunc<K>> struct ReverseIterator { typedef HashNode<T> Node; typedef ReverseIterator<K, T, Ref, Ptr, KeyOfT, Hash> self; typedef HashTable<K, T, KeyOfT, Hash> HT; ReverseIterator(Node* node, HT* ht) { it._node = node; it._ht = ht; } ReverseIterator(const Iterator<K, T,Ref,Ptr,KeyOfT,Hash> rit) { it._node = rit._node; it._ht = rit._ht; } ReverseIterator(const ReverseIterator<K, T, T&, T*, KeyOfT, Hash>& rit) { it._node = rit.it._node; it._ht = rit.it._ht; } Iterator<K, T, Ref, Ptr, KeyOfT, Hash> it; }; template<class K, class T, class KeyOfT,class Hash = HashFunc<K>> class HashTable { public: typedef HashNode<T> Node; typedef Iterator<K, T, T&, T*, KeyOfT, Hash> iterator; typedef ReverseIterator<K, T, T&, T*, KeyOfT, Hash> reverse_iterator; // 模板类友元,声明不需要默认参数 template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash> friend struct Iterator; public: // 成员方法 private: vector<HashNode<T>*> _tables; size_t n = 0; // vector 存储有效数据个数 }; }
对于迭代器、HashTable 的模板参数中, KeyOfT 的作用,和模拟实现 Map、Set 一样,就是为了取得数据的 Key 。因为 unordered_map 存储的是 pair<K,V> ,unordered_set 存储的是 K,为了使 HashTable 可以同时支持它们两个容器,所以有了 KeyOfT,该参数的传参是在 unordered_map 和 unordered_set 里面实现的。
如下,在 unordered_map 里面实现了 KeyOfMap,然后将仿函数传入 HashTable 的对应参数,这样在 HashTable 中,用 KeyOfT 实例化出的 kot 对象,实际上就是 struct KeyOfMap 的对象,再使用 kot 调用重载的 () 就可以拿到 pair<K,V> 中的 K 。
unordered_set 同理,不多赘述。
#include"HashTable.h" namespace simulate_unorderedmap { template<class K, class V,class Hash=HashBucket::HashFunc<K>> class unordered_map { public: struct KeyOfMap { const K& operator()(const pair<const K, V>& kv) { return kv.first; } }; public: // 成员函数 private: HashBucket::HashTable<K, pair<const K, V>, KeyOfMap,Hash> _map; }; }
这里可以使用包装器的设计模式,实现正向迭代器,然后反向迭代器实际上就是对正向迭代器的封装,反向迭代器的++,就是正向迭代器的–。
如下, Iterator 中有两个成员变量,一个是 HashNode 节点的指针,另一个是 HashTable 的指针。需要 _ht 这个成员变量的原因,其实是进行 ++ 、-- 所需要的。
对于成员函数的介绍主要就在 ++ 、-- 这两个。如下图, 我们可以理解为,vector 容器里面装的是一个个桶,桶是链表的结构。进行一次 ++ ,那么迭代器自然是指向链表中下一个节点,此时分为两种情况,下一个节点存在(如下图左边),下一个节点不存在,即当前节点已经是当前桶的最后一个节点(如下图右边)。 对于前者,自然很容易,但是,对于后者,就需要找到下一个有数据的桶,然后将 _node 指针指向桶内第一个节点,具体步骤如下。
要进行上面的操作,必须要有当前的 HashTable,但是用一整个 HashTable 当作成员变量,消耗太大,所以用指针。
operator- - () 也是类似的实现,只不过 - - 要先经过前三步得到桶号,然后判断当前节点的前面有没有节点,可以根据桶的第一个节点是不是当前节点进行判断,不难理解。
同时,无论是 ++ 还是 - - 都要注意边界条件,最后一个哈希桶的最后一个数据,进行 ++ 只能是 nullptr, 同样,第一个哈希桶的第一个数据,进行 - - 也只能是 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=HashFunc<K>> struct Iterator { public: typedef HashNode<T> Node; typedef Iterator<K, T, Ref, Ptr, KeyOfT, Hash> self; typedef HashTable<K, T, KeyOfT, Hash> HT; public: Iterator(Node* node = nullptr, HT* tables= nullptr) :_node(node) ,_ht(tables) {} Iterator(const Iterator<K,T,T&,T*,KeyOfT,Hash>& it) :_node(it._node) ,_ht(it._ht) {} Ptr operator->() { return &_node->_data; } Ref operator*() { return _node->_data; } bool operator!=(const self& it) { return _node != it._node; } self& operator++() { if (_node->_next) { _node = _node->_next; return *this; } // 找到下一个有数据的桶 KeyOfT kot; Hash hash; size_t hashi = hash(kot(_node->_data)) % _ht->_tables.size(); ++hashi; while (hashi < _ht->_tables.size()) { if (_ht->_tables[hashi]) { _node = _ht->_tables[hashi]; break; } else { ++hashi; } } if (hashi >= _ht->_tables.size()) _node = nullptr; return *this; } self& operator--() { // 找到当前桶 KeyOfT kot; Hash hash; int hashi = hash(kot(_node->_data)) % _ht->_tables.size(); // 桶的第一个就是当前节点构成的迭代器,往前面的桶找 if (_ht->_tables[hashi] == _node) { --hashi; while (hashi >= 0) { if (_ht->_tables[hashi] == nullptr) { --hashi; } else { // 找到上一个有数据的桶的最后一个数据 Node* cur = _ht->_tables[hashi]; while (cur->_next) cur = cur->_next; _node = cur; break; } } if (hashi < 0) _node = nullptr; } else // 桶的第一个不是当前节点构成的迭代器,在该桶里面找 { Node* cur = _ht->_tables[hashi]; while (cur->_next != _node) { cur = cur->_next; } _node = cur; } return *this; } Node* _node; HT* _ht; };
至于反向迭代器,那就是对正向迭代器的封装。
template<class K,class T,class Ref,class Ptr,class KeyOfT,class Hash=HashFunc<K>> struct ReverseIterator { typedef HashNode<T> Node; typedef ReverseIterator<K, T, Ref, Ptr, KeyOfT, Hash> self; typedef HashTable<K, T, KeyOfT, Hash> HT; ReverseIterator(Node* node, HT* ht) { it._node = node; it._ht = ht; } ReverseIterator(const Iterator<K, T,Ref,Ptr,KeyOfT,Hash> rit) { it._node = rit._node; it._ht = rit._ht; } ReverseIterator(const ReverseIterator<K, T, T&, T*, KeyOfT, Hash>& rit) { it._node = rit.it._node; it._ht = rit.it._ht; } self operator++() { return --it; } self& operator--() { return ++it; } Ref operator*() { return it._node->_data; } Ptr operator->() { return it._node->_data; } bool operator!=(const self& rit) { return it._node != rit.it._node; } Iterator<K, T, Ref, Ptr, KeyOfT, Hash> it; };
如下是 unordered_map、unordered_set 里面的 Find() 、Erase() 、Insert() 方法,其实就是对 HashTable 的封装。知道了 HashTable 里面的 Find() 、insert() 、Erase() 实现原理即可,并不难,都是和 ++ 类似的思路。
#include"HashTable.h" namespace simulate_unorderedmap { template<class K, class V,class Hash=HashBucket::HashFunc<K>> class unordered_map { public: struct KeyOfMap { const K& operator()(const pair<const K, V>& kv) { return kv.first; } }; public: typedef typename HashBucket::HashNode<pair<const K, V>> Node; typedef typename HashBucket::HashTable<K, pair<const K, V>, KeyOfMap, Hash>::iterator iterator; typedef typename HashBucket::HashTable<K, pair<const K, V>, KeyOfMap, Hash>::const_iterator const_iterator; typedef typename HashBucket::HashTable<K, pair<const K, V>, KeyOfMap, Hash>::reverse_iterator reverse_iterator; typedef typename HashBucket::HashTable<K, pair<const K, V>, KeyOfMap, Hash>::const_reverse_iterator const_reverse_iterator; iterator begin() { return _map.begin(); } iterator end() { return _map.end(); } pair<iterator,bool> insert(const pair<const K, V>& kv) { return _map.insert(kv); } iterator Find(const K& key) { Node* cur = _map.Find(key); return iterator(cur, &_map); } bool Erase(const K& key) { return _map.Erase(key); } V& operator[](const K& key) { pair<iterator, bool> ret = _map.insert(make_pair(key, V())); return ret.first->second; } private: HashBucket::HashTable<K, pair<const K, V>, KeyOfMap,Hash> _map; }; }
unordered_map 的 [ ] 是兼具插入和查找功能的。unordered_map 里面存储的是 pair<K,V> ,假设有一个 unordered_map< int,int >对象 m,执行 m[10]=1000; —— 如果存储的 pair<K,V> 里面,有 K 类型的数据是 10,那么就将这个键值对的 V 修改成 100;没有则插入 make_pair(10,100);
要完成上面描述的功能,就必须要让 [ ] 的返回值是 V& (V的引用),这样才可以修改 V 类型的数据。 可以通过迭代器来实现!!
如下是 unordered_map 里面实现 operator[ ] 的代码,_map 是 HashTable 实例化出来的对象,其 Insert() 返回值是 pair<iterator,bool> , 很明显,返回值包含两个信息:
V& operator[](const K& key)
{
pair<iterator, bool> ret = _map.insert(make_pair(key, V()));
return ret.first->second;
}
#pragma once #include<iostream> #include<vector> using namespace std; namespace HashBucket { template<class T> struct HashNode { HashNode<T>* _next; T _data; HashNode(const T& data) :_next(nullptr) , _data(data) {} }; template<class K> struct HashFunc { size_t operator()(const K& key) { return key; } }; // 模板的特化,如果有 string 类型的数据,优先使用特化的模板 template<> struct HashFunc<string> { size_t operator()(const string& s) { size_t hash = 0; for (auto& a : s) { hash += a; hash *= 31; } return hash; } }; // 前置声明,不需要默认参数 template<class K, class T, class KeyOfT, class Hash> class HashTable; template<class K,class T,class Ref,class Ptr,class KeyOfT,class Hash=HashFunc<K>> struct Iterator { public: typedef HashNode<T> Node; typedef Iterator<K, T, Ref, Ptr, KeyOfT, Hash> self; typedef HashTable<K, T, KeyOfT, Hash> HT; public: Iterator(Node* node = nullptr, HT* tables= nullptr) :_node(node) ,_ht(tables) {} Iterator(const Iterator<K,T,T&,T*,KeyOfT,Hash>& it) :_node(it._node) ,_ht(it._ht) {} Ptr operator->() { return &_node->_data; } Ref operator*() { return _node->_data; } bool operator!=(const self& it) { return _node != it._node; } self& operator++() { if (_node->_next) { _node = _node->_next; return *this; } // 找到下一个有数据的桶 KeyOfT kot; Hash hash; size_t hashi = hash(kot(_node->_data)) % _ht->_tables.size(); ++hashi; while (hashi < _ht->_tables.size()) { if (_ht->_tables[hashi]) { _node = _ht->_tables[hashi]; break; } else { ++hashi; } } if (hashi >= _ht->_tables.size()) _node = nullptr; return *this; } self& operator--() { // 找到当前桶 KeyOfT kot; Hash hash; int hashi = hash(kot(_node->_data)) % _ht->_tables.size(); // 桶的第一个就是当前节点构成的迭代器,往前面的桶找 if (_ht->_tables[hashi] == _node) { --hashi; while (hashi >= 0) { if (_ht->_tables[hashi] == nullptr) { --hashi; } else { // 找到上一个有数据的桶的最后一个数据 Node* cur = _ht->_tables[hashi]; while (cur->_next) cur = cur->_next; _node = cur; break; } } if (hashi < 0) _node = nullptr; } else // 桶的第一个不是当前节点构成的迭代器,在该桶里面找 { Node* cur = _ht->_tables[hashi]; while (cur->_next != _node) { cur = cur->_next; } _node = cur; } return *this; } Node* _node; HT* _ht; }; template<class K,class T,class Ref,class Ptr,class KeyOfT,class Hash=HashFunc<K>> struct ReverseIterator { typedef HashNode<T> Node; typedef ReverseIterator<K, T, Ref, Ptr, KeyOfT, Hash> self; typedef HashTable<K, T, KeyOfT, Hash> HT; ReverseIterator(Node* node, HT* ht) { it._node = node; it._ht = ht; } ReverseIterator(const Iterator<K, T,Ref,Ptr,KeyOfT,Hash> rit) { it._node = rit._node; it._ht = rit._ht; } ReverseIterator(const ReverseIterator<K, T, T&, T*, KeyOfT, Hash>& rit) { it._node = rit.it._node; it._ht = rit.it._ht; } self operator++() { return --it; } self& operator--() { return ++it; } Ref operator*() { return it._node->_data; } Ptr operator->() { return it._node->_data; } bool operator!=(const self& rit) { return it._node != rit.it._node; } Iterator<K, T, Ref, Ptr, KeyOfT, Hash> it; }; template<class K, class T, class KeyOfT,class Hash = HashFunc<K>> class HashTable { public: typedef HashNode<T> Node; typedef Iterator<K, T, T&, T*, KeyOfT, Hash> iterator; typedef Iterator<K, T, const T&, const T*, KeyOfT, Hash> const_iterator; typedef ReverseIterator<K, T, T&, T*, KeyOfT, Hash> reverse_iterator; typedef ReverseIterator<K, T, const T&, const T*, KeyOfT, Hash> const_reverse_iterator; // 模板类友元,声明不需要默认参数 template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash> friend struct Iterator; public: iterator begin() { Node* cur = nullptr; for (int i = 0; i < _tables.size(); i++) { cur = _tables[i]; if (cur) break; } return iterator(cur, this); } iterator end() { return iterator(nullptr, this); } const_iterator begin()const { Node* cur = nullptr; for (int i = 0; i < _tables.size(); i++) { cur = _tables[i]; if (cur) break; } return const_iterator(cur, this); } const_iterator end()const { return const_iterator(nullptr, this); } reverse_iterator rbegin() { int hashi = _tables.size() - 1; while (_tables[hashi] == nullptr && hashi>=0) { hashi--; } if (hashi < 0) return reverse_iterator(nullptr, this); // 最后一个 Node* cur = _tables[hashi]; while (cur->_next) cur = cur->_next; return reverse_iterator(cur, this); } reverse_iterator rend() { return reverse_iterator(nullptr, this); } const_reverse_iterator rbegin()const { int hashi = _tables.size() - 1; while (_tables[hashi] == nullptr && hashi >= 0) { hashi--; } if (hashi < 0) return const_reverse_iterator(nullptr, this); // 最后一个 Node* cur = _tables[hashi]; while (cur->_next) cur = cur->_next; return const_reverse_iterator(cur, this); } const_reverse_iterator rend()const { return const_reverse_iterator(nullptr, this); } bool Erase(const K& key) { KeyOfT kot; Hash hash; size_t hashi = hash(key) % _tables.size(); Node* cur = _tables[hashi]; Node* prev = nullptr; while (cur) { if (kot(cur->_data) == key) { if (prev == nullptr) { _tables[hashi] = cur->_next; } else { prev->_next = cur->_next; } delete cur; return true; } else { prev = cur; cur = cur->_next; } } return false; } Node* Find(const K& key) { if (_tables.size() == 0) return nullptr; Hash hash; KeyOfT kot; size_t hashi = hash(key) % _tables.size(); Node* cur = _tables[hashi]; while (cur) { if (kot(cur->_data) == key) return cur; cur = cur->_next; } return nullptr; } size_t GetNextPrime(size_t prime) { // SGI 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 }; size_t i = 0; for (; i < __stl_num_primes; ++i) { if (__stl_prime_list[i] > prime) return __stl_prime_list[i]; } return __stl_prime_list[i]; } pair<iterator,bool> insert(const T& data) { KeyOfT kot; Node* findnode = Find(kot(data)); if (findnode) return make_pair(iterator(findnode, this), false); Hash hash; if (_tables.size() == n) { //size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2; size_t newsize = GetNextPrime(_tables.size()); vector<Node*> newtables(newsize, nullptr); for (auto& cur : _tables) { while (cur) // 链表节点全部插入newht { Node* next = cur->_next; size_t newhashi = hash(kot(cur->_data)) % newsize; cur->_next = newtables[newhashi]; newtables[newhashi] = cur; cur = next; } } _tables.swap(newtables); } size_t hashi = hash(kot(data)) % _tables.size(); Node* tmp = new Node(data); tmp->_next = _tables[hashi]; _tables[hashi] = tmp; ++n; return make_pair(iterator(tmp,this),true); } size_t MaxBucketSize() { int size = _tables.size(); size_t max = 0; for (int i = 0; i < size; i++) { size_t tmp = 0; Node* cur = _tables[i]; while (cur) { tmp++; cur = cur->_next; } if (tmp > max) max = tmp; } return max; } private: vector<HashNode<T>*> _tables; size_t n = 0; // vector 存储有效数据个数 }; }
#include"HashTable.h" namespace simulate_unorderedmap { template<class K, class V,class Hash=HashBucket::HashFunc<K>> class unordered_map { public: struct KeyOfMap { const K& operator()(const pair<const K, V>& kv) { return kv.first; } }; public: typedef typename HashBucket::HashNode<pair<const K, V>> Node; typedef typename HashBucket::HashTable<K, pair<const K, V>, KeyOfMap, Hash>::iterator iterator; typedef typename HashBucket::HashTable<K, pair<const K, V>, KeyOfMap, Hash>::const_iterator const_iterator; typedef typename HashBucket::HashTable<K, pair<const K, V>, KeyOfMap, Hash>::reverse_iterator reverse_iterator; typedef typename HashBucket::HashTable<K, pair<const K, V>, KeyOfMap, Hash>::const_reverse_iterator const_reverse_iterator; iterator begin() { return _map.begin(); } iterator end() { return _map.end(); } pair<iterator,bool> insert(const pair<const K, V>& kv) { return _map.insert(kv); } iterator Find(const K& key) { Node* cur = _map.Find(key); return iterator(cur, &_map); } bool Erase(const K& key) { return _map.Erase(key); } V& operator[](const K& key) { pair<iterator, bool> ret = _map.insert(make_pair(key, V())); return ret.first->second; } private: HashBucket::HashTable<K, pair<const K, V>, KeyOfMap,Hash> _map; }; }
#include"HashTable.h" namespace simulate_unorderedset { template<class K, class Hash = HashBucket::HashFunc<K>> class unordered_set { public: typedef typename HashBucket::HashNode<K> Node; struct KeyOfSet { const K& operator()(const K& key) { return key; } }; public: typedef typename HashBucket::HashTable<K, K, KeyOfSet, Hash>::const_iterator iterator; typedef typename HashBucket::HashTable<K, K, KeyOfSet, Hash>::const_iterator const_iterator; typedef typename HashBucket::HashTable<K, K, KeyOfSet, Hash>::const_reverse_iterator reverse_iterator; typedef typename HashBucket::HashTable<K, K, KeyOfSet, Hash>::const_reverse_iterator const_reverse_iterator; pair<iterator,bool> insert(const K& key) { return _set.insert(key); } bool Erase(const K& key) { return _set.Erase(key); } iterator Find(const K& key) { Node* cur=_set.Find(key); return iterator(cur, &_set); } iterator begin() { return _set.begin(); } iterator end() { return _set.end(); } const_iterator begin()const { return _set.begin(); } const_iterator end()const { return _set.end(); } reverse_iterator rbegin() { return _set.rbegin(); } reverse_iterator rend() { return _set.rend(); } private: HashBucket::HashTable<K, K, KeyOfSet,Hash> _set; }; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。