当前位置: 首页 > news >正文

使用哈希表封装myunordered_set和myunordered_map

文章目录

  • 使用哈希表封装myunordered_set和myunordered_map
    • 实现出复用哈希表框架,并支持insert
    • 支持迭代器的实现
    • const
    • Key不能被修改
    • unordered_map支持[ ]
    • 结语

我们今天又见面啦,给生活加点impetus!!开启今天的编程之路!!
在这里插入图片描述
今天我们使用前面已经实现过的哈希表来实现myunordered_set和unordered_map
作者:٩( ‘ω’ )و260
我的专栏:C++进阶,C++初阶,数据结构初阶,题海探骊,c语言
欢迎点赞,关注!!

使用哈希表封装myunordered_set和myunordered_map

实现出复用哈希表框架,并支持insert

与map和set相比而言,unordered系列的实现更加复杂。
首先,unoedered_set是key类型,unordered_map是key,value类型,要实现一个底层实现两种数据结构,我们必须使用模版,这里我们传递三个参数,第一个是K,第二个是T,第三个是KeyOfT
三个模版的作用

第一个:传递K是为了find和erase接口,因为find和erase接口只能够使用key作为实参
第二个:代表哈希表中结点的存储类型
第三个:因为我们来寻找映射位置的时候需要取模,key一般可以直接取,但是pair必须需要取出其中的key

接下来我们来看代码:

template<class K>
class myunordered_set
{struct SetOfT{const K& operator(const K&key){return key;}}
public:private:HashTable<K,K,SetOfT> _tables;//底层结构是哈希桶
}
template<class K,class V>
class myunordered_map
{struct MapOfT{K& operator()(const pair<K,V>&kv){return kv.first;}}
private:HashTable<K,pair<K,V>,MapOfT> _tables;底层结构是哈希桶
}

因为存储的数值不确定,所以哈希结点也需要修改:

template<class T>
struct HashNode
{T _data;HashNode<K>*_next;HashNode(const T&data):_data(data),_next(nullptr){}
}

接下来我们来写insert函数,insert是重点,我们主要还写写的是伪代码。
Insert操作还是主题思路不变。
1:先求key映射的偏移位置(细节:key可能需要取,其次,可能需要转换成整形)这里会用到两层仿函数
2:不断头插
3:检查是否需要扩容(细节:定义的是哈希数组,而非一个哈希表,直接把结点给他拿下来就行)
4:注意返回值:我们直接模仿库中的insert函数返回pair<iterator,bool>类型(为什么我们实现这个insert函数重载?因为为了实现unordered_map支持operator[ ]做准备)
在这里插入图片描述
来看代码实现
注意:此时迭代器里面有两个成员变量,一个是_node,一个是哈希表,原因会在下面说.

pair<iterator,bool> Insert()(const T&data)
{KeyOfT kot;//取keyHash hs;//转整形,只有在取模的时候才用iterator it=Find(kot(data));if(it!=End()) return {it,false};//插入失败+去重if (_n == _tables.size()){//两种逻辑,优先第二种,第一种是拷贝结点,删除旧结点,第二种是直接把结点给拿下来//第一种:创建新的哈希表,第二种:创建新的数组//HashTable<K, V, Hash> newht(__stl_next_prime(_tables.size() + 1));//加1才能够继续取到更到的素数遍历旧表,将旧表的数据全部重新映射到新表//for (int i = 0;i < _tables.size();i++)//{//	Node* cur = _tables[i];//	while (cur)//	{//		newht.Insert(cur->_kv);//		cur = cur->_next;//	}//}//_tables.swap(newht._tables);//为什么第一种方法会重新再拷贝构造,因为复用的时候会调用new这个关键字vector<Node*> newtables(__stl_next_prime(_tables.size() + 1),nullptr);//数组初始化为n个nullptrfor (size_t i = 0;i < _tables.size();i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;//这里我们存储一下cur的下一个位置,因为当我们将cur下一个位置放下来的时候,_next被修改,所以需要提前记录一下//重新计算映射位置size_t hashi = hs(kot(cur->_data)) % newtables.size();cur->_next = newtables[hashi];newtables[hashi] = cur;cur = next;}//因为原来的旧链表的值已经被使用过了,所以我只直接将其置为nullptr_tables[i] = nullptr;}//底层是数组,例如vector或者string,调用库中的交换函数_tables.swap(newtables);}size_t hashi = hs(kot(data)) % _tables.size();Node* newnode = new Node(data);//这里不是new Node(kot(data));//pair类型结点里面存的是pair,不是key//优先选择头插进入链表,尾插需要向后遍历,头插分两种情况,映射位置为空或有数据,下面的代码两者都可以适用newnode->_next = _tables[hashi];_tables[hashi] = newnode;_n++;return {Iterator(newnode,this),true};
}

支持迭代器的实现

首先,因为哈希表中成员是一个单链表,所以我的的迭代器肯定是单向迭代器,即我们的迭代器只能支持++,不支持- -,同样也不支持随机访问。

迭代器的成员变量肯定是有一个_node。
当我们需要返回下一个位置的迭代器的时候,如果下一个位置不为空,那么迭代器中的指针直接向后走一步就可以了,如果下一个位置为空,就需要遍历到下一个不为空的位置的桶,返回桶的第一个位置即可。
此时我的cur都在遍历这个单链表了,我们根本找不到哈希表中下一个不为空的位置,那我们应该怎样找到下一个不为空的桶呢?我们必须传递一个哈希表过去!!
来看代码实现:
细节:这里我直接加上普通迭代器和const迭代器一起实现了,不然等下解释的太绕了
实现const迭代器的话,就会多2个模版参数,主要是返回值operator*和operator->。
而且,在迭代器内部有this指针,我们只用操作_node的指向即可

template<class K,class T,class Ref,class Ptr,class KeyOfT,class Hash>
struct HTIterator
{typedef HashNode<T> Node;typedef HashTable<K,T,KeyOfT,Hash> HT;typedef HTIterator<K,T,Ref,Ptr,keyofT,Hash>  Self;Node*_node;//成员变量HT*_pht;//成员变量Self& operator++(){if(_node->_next)//指向的下一个不为空{_node=_node->_next;}else{//下一个结点为空,需要向后找到不为空的桶KeyOfT kot;Hash hs;size_t hashi=hs(kot(_node->_data))%_pht->_tables.size();hashi++;//需要跳过当前桶while(hashi<_pht->_tables.size()){if(_pht->_tables[hashi]){_node=_pht->_tables[hashi];break;}++hashi;}if(hashi == _pht->_tables.size())//哈希表遍历遍历完了都还没有找到{_node=nullptr;}}return *this;}
}

接下来我再来实现迭代器中不重要的操作:
直接看代码即可:

Ref operator*()
{return _node->_data;
}
Ptr operator->()
{return &_node->_data;//箭头会返回对应结点的指针,编译器为了优化,自己会再添加一个箭头,这个箭头就是*.操作的结合
}
bool operator==(const Self& s)
{return _node == s._node;
}
bool operator!=(const Self& s)
{return _node != s._node;
}

const

const迭代器中迭代器部分我们已经完成了,接下来我们来看HashTable的部分。
这里需要解释Begin(),begin肯定是返回桶中链表的第一个结点即可

因为比较简单,直接来看代码即可:

Iterator Begin()//返回第一个桶的第一个结点
{if (_n == 0)return End();//此时没有结点,Begin()就是End()for (size_t i = 0;i < _tables.size();i++){if (_tables[i])return Iterator(_tables[i] ,this);}return End();//语法逻辑上来说必须要加,不能用运行逻辑来概括语法逻辑,万一里面的程序出错,可能就没有返回值了
}Iterator End()
{return Iterator(nullptr, this);
}//错误积累:如果报错不能将initializer list转换成啥,大概率编译器是将{}识别成这个作用了,但是我的目的是多参数来进行隐式类型转换
Const_Iterator Begin() const//返回第一个桶的第一个结点
{if (_n == 0)return End();for (size_t i = 0;i < _tables.size();i++){if (_tables[i]) return Const_Iterator(_tables[i], this);}return End();
}Const_Iterator End()const
{return Const_Iterator(nullptr, this);
}

Key不能被修改

这个也比较简单,只要将第二个模版参数对应的K修改为const K即可,随后对应的typedef部分也需要修改一下。

unordered_map支持[ ]

因为前面已经实现过,直接来看代码:

V& operator[](const K& key)
{pair<iterator, bool> ret = Insert({key,V()});//直接复用上面的Insert,本质上还是调用的底层return ret.first->second;
}

结语

感谢大家阅读我的博客,不足之处欢迎指正,感谢大家的支持
逆水行舟,楫摧而志愈坚;破茧成蝶,翼湿而心更炽!!加油!
在这里插入图片描述

相关文章:

  • leetcode:58. 最后一个单词的长度(python3解法)
  • Centos7 中 Docker运行配置Apache
  • 【Shell的基本操作】
  • 第九天——贪心算法——非递减数组
  • Promise.all 详解
  • ch10 题目参考思路
  • 突围“百机大战”,云轴科技ZStack智塔获IDC中国AI大模型一体机推荐品牌
  • 文章记单词 | 第87篇(六级)
  • Android App CAN通信测试
  • elementUI源码学习
  • OpenCV CUDA模块中矩阵操作------归一化与变换操作
  • 反射机制详细说明
  • 2025年渗透测试面试题总结-安恒[实习]安全服务工程师(题目+回答)
  • 遥感图像露天矿区检测数据集VOC+YOLO格式1542张1类别
  • 智能裂变引擎 商业增长利器 —— 专业推客系统耀世而来
  • Sprnig MVC 如何统一异常处理 (Exception Handling)?
  • Java—— 方法引用 : :
  • HANA数据库死锁
  • 2025年渗透测试面试题总结-安恒[社招]售前工程师(题目+回答)
  • Flink SQL、Hudi 、Doris在数据上的组合应用
  • 上海老字号卖黄金,与动漫IP联名两周销售额近亿元
  • 病重老人被要求亲自取钱在农业银行门口去世?株洲警方介入
  • 当番茄霸总遇上晋江古言,短剧IP小变局
  • 普京批准俄方与乌克兰谈判代表团人员名单
  • “大鼻子情圣”德帕迪约因性侵被判缓刑,还有新的官司等着他
  • 梅花奖在上海丨陈丽俐“婺剧折戏专场”:文戏武做,武戏文唱