赞
踩
目录
3. 解决unordered_set中Key可以修改的Bug
在学习封装unordered_map与unordered_set前,建议先学习如何封装map & set该篇文章用红黑树封装map&set【C++】-CSDN博客
这样更能理解其中的封装思想(两种封装方式,主题思路大体相似)
同时用哈希表封装 unordered_map和undordered_set,在封装之前,我们首先是要学会哈希表基本的精华,哈希实现建议大家先学习从底层认识哈希表【C++】-CSDN博客
由于我们前面已经学习过map与set的封装,在学习unordered_map(set) 封装这里我们直接给出基本的框架代码啦!
本质上:还是对哈希表进行封装,在unordered_map层面调用哈希底层。
用哈希表封装 unordered_map & unordered_set,思想核心:就是让unordered_set如何适应unordered_map,看到这里的都是已经学习过红黑树封装map与set的童鞋了吧
- namespace hash_map_set
- {
- template <class K>
- class unordered_set
- {
- public:
- struct SetkeyofT // 从T类型中提取Key
- {
- const K& operator()(const K& key)
- {
- return key;
- }
- };
-
- bool insert(const K& data)
- {
- return table.insert(data);
- }
-
- private:
- hash_barrel::HashTable<K, K, SetkeyofT> table;
- };
-
- template <class K, class V>
- class unordered_map
- {
- public:
-
- struct MapkeyofT
- {
- const K& operator()(const pair<const K, V>& kv)
- {
- return kv.first;
- }
- };
-
- bool insert(const pair<const K, V>& kv)
- {
- return table.insert(kv);
- }
- private:
- hash_barrel::HashTable<K, pair<const K, V>, MapkeyofT> table;
- };
- }
- // 哈希表底层部分变化,为了避免冗余,就只展现部分代码
-
- template <class T>
- struct Node_Data
- {
- typedef Node_Data<T> Node_data;
- T _data;
- Node_data* _downstars = nullptr;
-
- Node_Data(const T& pa = T())
- :_data(pa)
- {}
- };
-
- template <class K, class T, class KeyofT ,class Hashstr = Hashstr<K>>
- class HashTable
- {
- public:
- typedef Node_Data<T> Node_Data;
-
- .......

首先我们先完成 迭代器基本框架 和 基本“ * ”, “ & ”,“ ->”, " != "实现,代码其实比较简单,重要的是逻辑如何行云流水的形成,多写多看多思考吧!!
- template <class T>
- struct Node_Data
- {
- typedef Node_Data<T> Node_data;
- T _data;
- Node_data* _downstars = nullptr;
-
- Node_Data(const T& pa = T())
- :_data(pa)
- {}
- };
-
- template <class type>
- struct Hashstr
- {
- int operator()(const type& sd)
- {
- return sd;
- }
- };
-
- template<>
- struct Hashstr<string>
- {
- size_t operator()(const string& str)
- {
- size_t sum = 0;
- for (auto e : str)
- {
- sum += e;
- sum *= 31; // 别问,问就是实验的结果
- }
- return sum;
- }
- };
-
- // 由于迭代器需要使用HashTable作为成员变量,因此需要前置声明
- template <class K, class T, class KeyofT, class Hashstr = Hashstr<K>>
- class HashTable;
-
- template <class K, class T, class KeyofT, class Ref, class Ptr, class Hash>
- struct HT_iterator
- {
- typedef Node_Data<T> Node_Data;
- typedef HashTable<K, T, KeyofT, Hash> HashTable;
- typedef HT_iterator<K, T, KeyofT, Ref, Ptr, Hash> HT_iter; // 正常迭代器
-
- Node_Data* _node; // 当前位置
- HashTable* _ht; // 当前哈希表
- size_t _bucket = -1; // 当前桶
-
- HT_iterator(Node_Data* node, HashTable* ht)
- :_node(node)
- , _ht(ht)
- {
- Hash hashstr;
- KeyofT keyofT;
- if (node) // node为空的时候,我们会对nullptr进行访问,所以规避一下
- _bucket = hashstr(keyofT(_node->_data)) % _ht->Get_tables().size();
- }
-
- // HT_iterator(const HT_iterator)
-
- Ref operator*()
- {
- return _node->_data;
- }
-
- Ptr operator->()
- {
- return &(_node->_data);
- }
-
- bool operator!=(const HT_iter& it)
- {
- return _node != it._node;
- }
- };

思路:从第一个桶开始访问数据,访问完一个数据后,接着下一个桶,直至结束。
思路比较简单,但迭代器设计,逻辑比较难处理。
-
- // 哈希迭代器只往前单向
- HT_iter operator++()
- {
- // 三种情况:
- // 1. 下一个为空,下一个桶存在
- // 2. 下一个为空,下一个不存在
- // 3. 下一个存在
- if (_node->_downstars)
- { // 3.
- _node = _node->_downstars;
- return *this;
- }
- else
- { // 1 , 2
- 寻找下一个不为空的桶
- ++_bucket;
- while (_bucket < _ht->Get_tables().size())
- {
- _node = (_ht->Get_tables())[_bucket];
- if (_node) // 找到后中断寻找
- break;
- ++_bucket;
- }
- return *this;
- }
- }

在实现operator[]时,我们需要先将insert,find函数进行优化:
让insert返回值从bool ——> 返回pair<iterator, bool>;
find()从返回数据指针——> 返回迭代器;
- // Hashtable——哈希类中
- T& operator[](const T& pa)
- {
- pair<HT_iterator, bool> it = insert(pa);
- return *(it.first);
- }
-
- // 封装层 unordered_map
- V& operator[](const K& pa)
- {
- pair<HT_iterator, bool> ret = insert(make_pair(pa, V()));
- return ret.first->second;
- }
-
- // unordered_set
- K& operator[](const K& pa)
- {
- return table[pa];
- }

在哈希实现过程中,我们实现了该仿函数,目的就是为了解决当 K不是int的时候(比如string类)无法转化为size_t类型,来寻找哈希地址。
那么问题来了!! 我如果想将Day类型(处理年,月,日的类)作为K呢?? 请问这怎么将这个日期类转化为size_t???
为了方便分析,我将这个将其他数据转化为size_t的类叫做转化类
我们会发现,封装在底层的中转化类(int与string类)在目前看来,应该将其放在unordered_map与unordered_set的封装层;同时在两个封装层添加一个新的参数,作为转化类的行参。
这样做的好处是,在我们不知道K类型时,用户可以导入其自己设定的转化类,来实现各种类(各种类型)转化为size_t类型。
在前面的文章中(用红黑树封装map&set【C++】-CSDN博客),我们已经修复过一次了,这里就只展现修改位置了。
- namespace hash_map_set
- {
-
- template <class type>
- struct Hashstr
- {
- int operator()(const type& sd)
- {
- return sd;
- }
- };
-
- template<>
- struct Hashstr<string>
- {
- size_t operator()(const string& str)
- {
- size_t sum = 0;
- for (auto e : str)
- {
- sum += e;
- sum *= 31; // sum为啥都要乘31这个数??1.减少数据聚集在一个桶中。2.31这个数是大量实验的最佳数
- }
- return sum;
- }
- };
-
- template <class K, class Hash = Hashstr<K>>
- class unordered_set
- {
- public:
- struct SetkeyofT // 从T类型中提取Key
- {
- const K& operator()(const K& key)
- {
- return key;
- }
- };
-
- // 关于为什么要这样操作?原因:底层(HT_iterator)实例化后,再取得模板,而不是自己设定
- typedef typename hash_barrel::HashTable<K, K, SetkeyofT, Hash>::const_HT_iterator HT_iterator;
- typedef typename hash_barrel::HashTable<K, K, SetkeyofT, Hash>::const_HT_iterator const_HT_iterator;
-
- const_HT_iterator begin() const
- {
- return table.begin();
- }
-
- const_HT_iterator end() const
- {
- return table.end();
- }
-
- HT_iterator begin()
- {
- return table.begin();
- }
-
- HT_iterator end()
- {
- return table.end();
- }
-
- pair<HT_iterator, bool> insert(const K& data)
- {
- return table.insert(data);
- }
-
- K& operator[](const K& pa)
- {
- return table[pa];
- }
-
- HT_iterator find(const K& pa)
- {
- return table.find(pa);
- }
-
- bool erase(const K& pa)
- {
- return table.erase(pa);
- }
-
- private:
- hash_barrel::HashTable<K, K, SetkeyofT, Hash> table;
- };
-
- template <class K, class V, class Hash = Hashstr<K>>
- class unordered_map
- {
- public:
-
- struct MapkeyofT
- {
- const K& operator()(const pair<const K, V>& kv)
- {
- return kv.first;
- }
- };
-
- typedef typename hash_barrel::HashTable<K, pair<const K, V>, MapkeyofT, Hash>::HT_iterator HT_iterator;
- typedef typename hash_barrel::HashTable<K, pair<const K, V>, MapkeyofT, Hash>::const_HT_iterator const_HT_iterator;
-
- const_HT_iterator begin() const
- {
- return table.begin();
- }
-
- const_HT_iterator end() const
- {
- return table.end();
- }
-
- HT_iterator begin()
- {
- return table.begin();
- }
-
- HT_iterator end()
- {
- return table.end();
- }
-
- pair<HT_iterator, bool> insert(const pair<const K, V>& kv)
- {
- return table.insert(kv);
- }
-
- V& operator[](const K& pa)
- {
- pair<HT_iterator, bool> ret = insert(make_pair(pa, V()));
- return ret.first->second;
- }
-
- HT_iterator find(const K& pa)
- {
- return table.find(pa);
- }
-
- bool erase(const K& pa)
- {
- return table.erase(pa);
- }
-
- private:
- hash_barrel::HashTable<K, pair<const K, V>, MapkeyofT, Hash> table;
- };
- }

- namespace hash_barrel
- {
- template <class T>
- struct Node_Data
- {
- typedef Node_Data<T> Node_data;
- T _data;
- Node_data* _downstars = nullptr;
-
- Node_Data(const T& pa = T())
- :_data(pa)
- {}
- };
-
- // 由于迭代器需要使用HashTable作为成员变量,因此需要前置声明
- template <class K, class T, class KeyofT, class Hashstr>
- class HashTable;
-
- template <class K, class T, class KeyofT, class Ref, class Ptr, class Hash>
- struct HT_iterator
- {
- typedef Node_Data<T> Node_Data;
- typedef HashTable<K, T, KeyofT, Hash> HashTable;
- typedef HT_iterator<K, T, KeyofT, Ref, Ptr, Hash> HT_iter; // 正常迭代器
- typedef HT_iterator<K, T, KeyofT, T&, T*, Hash> iterator;
-
- Node_Data* _node; // 当前位置
- const HashTable* _ht; // 当前哈希表,添加const原因:1.迭代器不会修改哈希表 2.const迭代器传入的是const的哈希表,所以需要适配
- size_t _bucket = -1; // 当前桶
-
- HT_iterator(Node_Data* node, const HashTable* ht)
- :_node(node)
- , _ht(ht)
- {
- Hash hashstr;
- KeyofT keyofT;
- if (node) // node为空的时候,我们会对nullptr进行访问,所以规避一下
- _bucket = hashstr(keyofT(_node->_data)) % _ht->Get_tables().size();
- }
-
- // 1. 当对象是普通迭代器时,这是拷贝构造
- // 2. 当对象是const迭代器时,是利用普通迭代器构造const迭代器的构造函数
- HT_iterator(const iterator& it)
- :_node(it._node)
- ,_ht(it._ht)
- ,_bucket(it._bucket)
- {}
-
- // HT_iterator(const HT_iterator)
-
- Ref operator*()
- {
- return _node->_data;
- }
-
- Ptr operator->()
- {
- return &(_node->_data);
- }
-
- bool operator!=(const HT_iter& it)
- {
- return _node != it._node;
- }
-
- // 哈希迭代器只往前单向
- HT_iter operator++()
- {
- // 三种情况:
- // 1. 下一个为空,下一个桶存在
- // 2. 下一个为空,下一个不存在
- // 3. 下一个存在
- if (_node->_downstars)
- { // 3.
- _node = _node->_downstars;
- return *this;
- }
- else
- { // 1 , 2
- 寻找下一个不为空的桶
- ++_bucket;
- while (_bucket < _ht->Get_tables().size())
- {
- _node = (_ht->Get_tables())[_bucket];
- if (_node)
- break;
- ++_bucket;
- }
- return *this;
- }
- }
- };
-
-
-
- template <class K, class T, class KeyofT ,class Hash>
- class HashTable
- {
- public:
- typedef Node_Data<T> Node_Data;
- typedef HT_iterator<K, T, KeyofT, const T&, const T*, Hash> const_HT_iterator; // const迭代器
- typedef HT_iterator<K, T, KeyofT, T&, T*, Hash> HT_iterator; // 普通迭代器
-
- ~HashTable()
- {
- for (auto bucket : _tables)
- {
- Node_Data* cur = bucket;
- while (cur)
- {
- Node_Data* next = cur->_downstars;
- delete (cur);
- cur = next;
- }
- }
- }
-
- const_HT_iterator begin() const
- {
- size_t i = 0;
- Node_Data* cur = nullptr;
- for (; i < _tables.size(); i++)
- {
- cur = _tables[i];
-
- if (cur)
- break;
- }
-
- return const_HT_iterator(cur, this); // this就是哈希表对象
- }
-
- const_HT_iterator end() const
- {
- return const_HT_iterator(nullptr, this);
- }
-
- HT_iterator begin()
- {
- size_t i = 0;
- Node_Data* cur = nullptr;
- for (; i < _tables.size(); i++)
- {
- cur = _tables[i];
-
- if (cur)
- break;
- }
- return HT_iterator(cur, this); // this就是哈希表对象
- }
-
- HT_iterator end()
- {
- return HT_iterator(nullptr, this);
- }
-
- // 在实现 operator[]时,底层用到insert(),没找到,插入新值返回迭代器;找到直接返回迭代器
- pair<HT_iterator,bool> insert(const T& pa)
- {
- KeyofT keyofT;
- HT_iterator ret = _find(keyofT(pa));
- if (ret != end())
- return make_pair(ret, true);
-
- // 考虑扩容:负载因子为1,2,3都可以
- Hash hashstr;
- if (_tables.size() == 0 || _n * 10 / _tables.size() >= 10)
- {
- size_t new_size = _tables.size() == 0 ? 10 : _tables.size() * 2;
- // 开始扩容
- vector<Node_Data*> new_tables;
- new_tables.resize(new_size);
- for (auto& data : _tables)
- {
- // 处理桶内的数据,重新插入新节点
- Node_Data* cur = data;
- while (cur)
- {
- Node_Data* room = cur->_downstars;
- size_t new_hashi = hashstr(keyofT(cur->_data)) % new_tables.size();
- // 处理结点关系
- Node_Data* tmp = new_tables[new_hashi];
- new_tables[new_hashi] = cur;
- cur->_downstars = tmp;
- cur = room;
- }
- }
- _tables.swap(new_tables);
- }
-
- size_t hashi = hashstr(keyofT(pa)) % _tables.size();
- Node_Data* new_node = new Node_Data(pa);
- new_node->_downstars = _tables[hashi];
- _tables[hashi] = new_node;
- _n++;
- return make_pair(HT_iterator(new_node, this), true);
- }
-
- HT_iterator find(const K& order)
- {
- return _find(order);
- }
-
- bool erase(const K& order)
- {
- Hash hashstr;
- HT_iterator cur = find(order);
- if (!(cur._node))
- {
- cout << "擦除失败: 不存在" << endl;
- return false;
- }
- size_t index = hashstr(order) % _tables.size();
- Node_Data* tmp = _tables[index];
-
- while (tmp)
- {
- if (cur._node == tmp)
- {
- break;
- }
- else if (cur._node == tmp->_downstars)
- {
- break;
- }
- tmp = tmp->_downstars;
- }
- // 开始处理节点
- if (tmp == _tables[index]) // 如果擦除的是头,那要置空的包括指针数组
- {
- _tables[index] = cur._node->_downstars;
- }
- else
- {
- tmp->_downstars = cur._node->_downstars;
- }
-
- delete (cur._node);
- cout << "擦除成功" << endl;
- return true;
- }
-
- T& operator[](const T& pa)
- {
- pair<HT_iterator, bool> it = insert(pa);
- return *(it.first);
- }
-
- size_t Get_n()
- {return _n; }
-
- vector<Node_Data*>& Get_tables()
- {
- return _tables;
- }
-
- // 需要为const 对象进行重载
- const vector<Node_Data*>& Get_tables() const
- {
- return _tables;
- }
-
- private:
- // Node_Data* _find(const K& order)
- HT_iterator _find(const K& order)
- {
- if (!_tables.size())
- return end();
-
- Hash hashstr;
- KeyofT keyofT;
- size_t hashi = hashstr(order) % _tables.size();
- auto cur = _tables[hashi];
- while (cur)
- {
- if (keyofT(cur->_data) == order)
- return HT_iterator(cur, this);
- cur = cur->_downstars;
- }
- return end();
- }
-
- vector<Node_Data*> _tables; // 存放各个哈希地址的第一个结点地址的指针数组
- size_t _n = 0; // 哈希桶中,数据个数
- };
- }

哈希表的查找,插入,删除操作平均为O(1);红黑树的查找,插入,删除平均O(logn)。因此哈希表会有更高的效率。
1. 哈希表存在,哈希冲突,而解决方案有 哈希桶法(链表法),开放定址法。如果超过负载值,将对全部数据进行重新分配,这会导致性能下降。
2. 占用更多的内存
1. 相比于哈希表红黑树占用内存比较少。
2. 相比于哈希表插入可能需要重新分配哈希桶,红黑树的插入效率不差,也更稳定。
因此:选择哈希表还是红黑树作为map的底层数据结构取决于具体的应用场景。如果对性能要求较高且不需要有序性,可以选择哈希表。如果需要有序性或者对性能要求不是特别高,可以选择红黑树。
下节预告:哈希应用!!
本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。