C++11引入的STL中的unordered系列关联式容器
作者:oi嘉菲猫
STL中的unordered系列容器是C++11引入的基于哈希表实现的关联式容器,与传统的红黑树实现的关联容器(map/set)相比,它们在元素存储和访问机制上有显著差异
1.补充认识
STL中的unordered系列容器的底层就是哈希表,包括unordered_set、unordered_map、unordered_multiset、unordered_multimap四个。
底层哈希原理
补充:从底层来说应该分别类似的命名为tree_map和hash_map,也就是树形结构和哈希结构的,但是由于map之类的出来的早,后面的为了对应就把hash实现的命名为unordered_xx。
2.unordered_set
2.1容器原型
- unordered_set是存储key键值的关联式容器,其允许通过keys快速的索引到与其对应的value。
- 在unordered_set中,键值通常用于惟一地标识元素。
- 在内部,unordered_set没有对kye按照任何特定的顺序排序, 为了能在常数范围内找到key,unordered_set将相同哈希值的键值放在相同的桶中。
- unordered_set容器通过key访问单个元素要比set快,但它通常在遍历元素子集的范围迭代方面效率较低。
- 它的迭代器至少是前向迭代器。
2.2常用接口
3.unordered_multiset
和set与multiset的区别一样,unordered_multiset相对于unordered_set支持相同关键码的存放。
接口区别:
4.unordered_map
4.1容器原型
- unordered_map是存储<key, value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value。
- 在unordered_map中,键值通常用于惟一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
- 在内部,unordered_map没有对<kye, value>按照任何特定的顺序排序, 为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中。
- unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
- unordered_maps实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value。
- 它的迭代器至少是前向迭代器。
4.2常用接口
与unordered_set相比,unordered_map就是键值对KV模型,并且提供了[]重载和at接口,这两个与map相似功能。
其他接口功能与unordered_set相同。
5.unordered_multimap
unordered_multimap和unordered_map的区别就是unordered_multiset和unordered_multiset的区别。
另外就是unordered_multimap不支持[]和at。
6.树形关联容器和哈希关联容器的对比
特性 | 哈希容器 | 树形容器 |
底层结构 | 哈希表(顺序表+顺序表/红黑表) | 红黑树(平衡二叉搜索树) |
元素顺序 | 无序 | 有序 |
平均时间复杂度 | O(1) | O(log_2 N) |
最坏时间复杂度 | O(N)(所有元素都冲突,实际几乎不可能) | O(log_2 N) |
迭代器 | 单向迭代器 | 双向迭代器 |
关键特性 | 增删查改都很快,遍历较慢。性能依赖哈希函数。 | 插入后自然有序。 |
适用场景 | 快速查找,不关注顺序。 | 需要元素有序。 |
小总结:除非有有序的要求,不然都可以先考虑哈希容器。
7.unordered系列模拟实现
7.1重要补充
1)具体存放什么数据类型应该是set和map层面决定的,底层的哈希表只是存放数据,所以需要一个从存放数据提取关键码的KeyOfT反函数。
2)另外还需要一个哈希函数来处理不能直接取模的情况。
3)哈希表的迭代器迭代逻辑:单链表迭代+顺序表的哈希桶迭代。
4)如果使用自定义类型取作为树形关联容器的关键码的话,需要提供一个小于比较仿函数,有了小于也就有了大于和等于。
5)如果使用自定义类型取作为哈希关联容器的关键码的话,需要提供一个等于比较仿函数。
6)set的迭代器无论是普通迭代器还是const迭代器在底层都是使用const迭代器。
7.2代码
源码
7.2.1哈希表
#pragma once #include <iostream> // 使用哈希桶解决哈希表的哈希冲突 #include <vector> #include <utility> using namespace std; namespace lbq { // 将输入转成整型的哈希函数 template<class K> struct HashFunc { size_t operator()(const K& key) { return (size_t)key; } }; // 针对常用的字符串类型的特化 template<> struct HashFunc<string> { size_t operator()(const string& str) { size_t ret = 0; for(auto& ele : str) { ret = ret * 131 + ele; } return ret; } }; // 哈希桶具体存储的数据类型 // 实际为一个单链表节点 template<class T> struct _HashNode { T _data; _HashNode<T>* _next; _HashNode(const T& data) : _data(data) , _next(nullptr) {} }; // 哈希表的迭代器,其实是顺序表迭代+链表迭代 template<class K, class T, class KeyOfT, class Hash > class HashTable; // 迭代器中需要访问,提前声明 template<class K, class T, class KeyOfT, class Hash> struct _HashTableIterator { typedef _HashNode<T> Node; typedef HashTable<K, T, KeyOfT, Hash> HT; typedef _HashTableIterator<K, T, KeyOfT, Hash> Iterator; Node* _node; HT* _ht; _HashTableIterator(Node* node, HT* ht) : _node(node) , _ht(ht) {} bool operator==(const Iterator& it)const { return _node == it._node; } bool operator!=(const Iterator& it)const { return _node != it._node; } T& operator*() { return _node->_data; } T* operator->() { return &(_node->_data); } Iterator operator++() { if(_node->_next != nullptr) { _node = _node->_next; } else { // 找下一个桶的位置 size_t table_index = Hash()(KeyOfT()(_node->_data)) % _ht->_table.size(); while(++table_index < _ht->_table.size()) { if(_ht->_table[table_index] != nullptr) { _node = _ht->_table[table_index]; break; } } // 如果后面已经没有桶了 if(table_index == _ht->_table.size()) { _node = nullptr; } } return *this; } Iterator operator++(int) { Iterator tmp(_node, _ht); operator++(); return tmp; } }; // 使用除留余数法作为哈希表的哈希函数,使用哈希桶解决冲突 // K:关键码数据类型 // T:存储的数据类型,Key或者pair<K, V> // KeyOfT:从存储的数据中提取关键码 // Hash:将关键码转成整型用于取模运算 template<class K, class T, class KeyOfT, class Hash> class HashTable { private: typedef _HashNode<T> Node; friend struct _HashTableIterator<K, T, KeyOfT, Hash>; public: typedef _HashTableIterator<K, T, KeyOfT, Hash> iterator; iterator begin() { for(size_t i = 0; i < _table.size(); i++) { if(_table[i] != nullptr) { return iterator(_table[i], this); } } return end(); } iterator end() { return iterator(nullptr, this); } //HashTable() //{ // _table.resize(10, nullptr); // _sz = 0; //} ~HashTable() { for(size_t i = 0; i < _table.size(); i++) { Node* cur = _table[i]; while(cur != nullptr) { Node* next = cur->_next; delete cur; cur = next; } _table[i] = nullptr; } } pair<iterator, bool> insert(const T& data) { Hash hash; KeyOfT kot; iterator find_it = find(kot(data)); if(find_it != end()) { // 已经存在相同的元素 return make_pair(find_it, false); } if(_table.size() == 0 || _sz == _table.size()) { // 顺序表为空,或者载荷因子大于1时增容 size_t new_size = _table.size() == 0 "h14">7.2.2unordered_set
#pragma once // hash set模拟实现 #include "hashtable.hpp" namespace lbq { template<class K, class Hasher = HashFunc<K>> class unordered_set { private: struct SetKeyOfT { const K& operator()(const K& key) { return key; } }; typedef HashTable<K, K, SetKeyOfT, Hasher> HT; public: typedef typename HT::iterator iterator; iterator begin() { return _ht.begin(); } iterator end() { return _ht.end(); } bool empty() { return _ht.empty(); } size_t size() { return _ht.size(); } iterator find(const K& key) { return _ht.find(key); } size_t count(const K& key) { return _ht.count(key); } pair<iterator, bool> insert(const K& key) { return _ht.insert(key); } bool erase(const K& key) { return _ht.erase(key); } size_t bucket_count() { return _ht.bucket_count(); } size_t bucket_size(size_t n) { return _ht.bucket_size(n); } private: HT _ht; }; }
7.2.3unordered_map
#pragma once // unorder_map 模拟实现 #include "hashtable.hpp" namespace lbq { template<class K, class V, class Hasher = HashFunc<K>> class unordered_map { private: typedef pair<K, V> T; struct MapKeyOfT { const K& operator()(const T& kv) { return kv.first; } }; typedef HashTable<K, T, MapKeyOfT, Hasher> HT; public: typedef typename HT::iterator iterator; iterator begin() { return _ht.begin(); } iterator end() { return _ht.end(); } size_t size() { return _ht.size(); } bool empty() { return _ht.empty(); } V& operator[](const K& key) { pair<iterator, bool> ret = _ht.insert(make_pair(key, V())); return ret.first->second; } iterator find(const K& key) { return _ht.find(key); } size_t count(const K& key) { return _ht.count(key); } pair<iterator, bool> insert(const T& kv) { return _ht.insert(kv); } bool erase(const K& key) { return _ht.erase(key); } size_t bucket_count() { return _ht.bucket_count(); } size_t bucket_size(const size_t& n ) { return _ht.bucket_size(n); } private: HT _ht; }; }
到此这篇关于C++11引入的STL中的unordered系列关联式容器的文章就介绍到这了,更多相关C++的STL中的unordered容器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!