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

C++STL系列之unordered_set和unordered_map


前言

unordered_set和unordered_map仍然是关联式容器,底层是哈希表。跟上次讲的multimap和multiset类似,也有unordered_multiset和unordered_multimap。


一、unordered_set和unordered_map

unordered意思为无序的,所以他的迭代器遍历不是有序的,和set、map用法类似,不详细介绍了,介绍一下用法上不一样的。这两个迭代器都是单向迭代器,只支持++,不支持- - ,因为内部挂的是单链表,既然内部无序,就不支持lower_bound这种需要容器有序的操作
在这里插入图片描述
前两个参数老朋友了,第三个就是哈希函数,库里是有hash函数的,想使用直接使用也可以,第四个是判断相不相等,如果不传,就是默认哈希函数算出来的值相等就是相等,自己传就是自定义相等。
另外:其实还多了一点接口,但是作用不大,熟悉拉链法应该能看懂大概
第一块就是关于桶的各种接口
第二块类似开空间,负载因子这种
第三块得到各种内部函数,比如hash函数等等,其实作用都不大,重要的还是上次讲到那些比如insert,erase等等

二、unordered_set和unordered_map的模拟实现

基本框架

有了上次的经验,这次会快一点,但是到迭代器部分还需要仔细讲,
对于哈希表,一共四个参数,K,T,KofT,HashFunc

template<class K, class T, class KofT,class HashFunc = DefaultHashFunc<K>>
class HashTable
{typedef HashData<T> Node;
private:vector<Node*> _table;int _n;//存储有效数据
};

当然insert还需要改
框架先搭上

template <class K>
class unordered_set {struct SetKeyOfT{const K& operator()(const K& key){return key;}};
private:HashTable<K, K,SetKeyOfT> _table;
};```cpp
template <class K, class V>
class unordered_map
{ 
private:struct MapKeyOfT{const K& operator()(const pair<K,V>& key){return key.first;}};
public:
private:HashTable<K, pair<const K, V>,MapKeyOfT> _table;
};

Hash_Iterator

迭代器怎么设计,既然是单向,主要考虑++就可以
在这里插入图片描述

迭代器:

template<class K,class T,class Ref,class Ptr,class KofT,class HashFunc>
class Hash_Iterator
{
public:typedef HashData<T> Node;typedef Hash_Iterator<K, T,Ref, Ptr,KofT,HashFunc> Self;HashTable<K, T, KofT, HashFunc>* _ht;Node* _node;typedef Hash_Iterator<K, T, T&, T*, KofT, HashFunc> iterator;Hash_Iterator(Node* node, HashTable<K, T, KofT, HashFunc>* ht):_node(node),_ht(ht){}Hash_Iterator(const iterator& it):_node(it._node),_ht(it._ht){ }Ref operator*(){return _node->_kv;}Ptr operator->(){return &(operator*());}Self& operator++(){if (_node->_next != nullptr){_node = _node->_next;return *this;}else{KofT koft;HashFunc hs;size_t  hashi = hs(koft(_node->_kv)) % _ht->_table.size();++hashi;while(hashi < _ht->_table.size()){if (_ht->_table[hashi] != nullptr){_node = _ht->_table[hashi];return *this;}else{++hashi;}}_node = nullptr;return *this;}}Self operator++(int){Self tmp(*this);++(*this);return tmp;}bool operator==(const Self& ot)const{return _node == ot._node;}bool operator!=(const Self& ot)const{return _node != ot._node;}
};

注意:这里六个参数要知道都是干什么的,以及计算hashi要套两层函数,第一层是取到key,第二层是哈希函数。

然后套begin和end

	template<class K, class T, class KofT,class HashFunc = DefaultHashFunc<K>>class HashTable{typedef HashData<T> Node;public:template<class K, class T, class Ref, class Ptr,class KofT, class HashFunc>typedef Hash_Iterator<K,T,T&,T*,KofT,HashFunc> iterator;typedef Hash_Iterator<K,T,const T&,const T*,KofT,HashFunc> const_iterator;iterator begin(){for (size_t i = 0; i < _table.size(); ++i){if (_table[i] != nullptr){return iterator(_table[i], this);}}return iterator(nullptr, this);}iterator end(){return iterator(nullptr,this);}.....}

这里就提现出为什么传哈希表的地址了,this指针就是哈希表的地址啊,直接传this就可以。

unordered_map、unordered_set里面的迭代器

复习一下上次:set的普通迭代器和const迭代器都是const迭代器,map正常,但是为了防止key修改加上const,记得加typename告诉编译器这是类型

unordered_map:
typedef typename HashTable<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
typedef typename HashTable< K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;
unordered_set:
typedef typename HashTable<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename HashTable<K, K, SetKeyOfT>::const_iterator const_iterator;

错误分析

到这以为迭代器整完了结果一跑,一万个错误,一个个分析一下。
先指出一个问题,就是迭代器的构造函数里不要给const Node*_node,因为const Node* 类型无法给Node*类型,所以不要任意的构造函数里的形参都加const,我们看看报了那些错误。
。。。这个编译器报的错误真是一言难尽,我们就直接分析了。

在这里插入图片描述

第一个问题的解决方案:

template<class K, class T, class KofT, class HashFunc>
class HashTable;
//前置声明一下

第二个:

template<class K, class T, class Ref, class Ptr,class KofT, class HashFunc>
friend class Hash_Iterator;

第三个:

const HashTable<K, T, KofT, HashFunc>* _ht;
Hash_Iterator( Node* node, const HashTable<K, T, KofT, HashFunc>* ht):_node(node),_ht(ht)
{}

operator[]

和map,set一样的套路,这里就简单提一遍过程。

V& operator[](const K& key)
{auto it = insert(make_pair(key,V())).first;return *it.second;
}

改的过程是:1.先改insert的返回值,改成pair<iterator,bool>,
2.改unordered_map和unordered_set的返回值
3.因为unordered_set的迭代器默认是const迭代器,返回类型不匹配,在哈希表迭代器中搞一个一直是普通迭代器的iterator,写一个构造,如果实例化是const迭代器就是普通迭代器构造const迭代器,如果是普通迭代器那就是他的拷贝构造。

  pair<iterator,bool> insert(const pair<K,V>& p)
{return _table.insert(p);
}
V& operator[](const K& key)
{iterator it = insert(make_pair(key, V())).first;return (*it).second;//
}
pair<iterator,bool> insert(const T& p)
{KofT koft;auto it = Find(koft(p));if (it != end()){return make_pair(it,false);}HashFunc h;if (_n == _table.size()){//扩容size_t newsize = GetNextPrime(_table.size());vector<Node*> newtable;newtable.resize(newsize, nullptr);for (size_t i = 0; i < _table.size(); ++i){Node* cur = _table[i];while (cur){Node* next = cur->_next;size_t newhashi = h(koft(p)) % newsize;cur->_next = newtable[newhashi];newtable[newhashi] = cur;cur = next;}_table[i] = nullptr;}_table.swap(newtable);//这里newtable会调用析构导致}size_t hashi = h(koft(p)) % _table.size();Node* newnode = new Node(p);//悬挂结点newnode->_next = _table[hashi];_table[hashi] = newnode;_n++;return make_pair(iterator(_table[hashi],this),true);
}
typedef Hash_Iterator<K, T, T&, T*, KofT, HashFunc> iterator;
Hash_Iterator(const iterator& it):_node(it._node),_ht(it._ht)
{ }

有一些说法说哈希桶的桶的数量取一些特定的质数可以减少冲突。取质数的方式:

size_t GetNextPrime(size_t prime)
{const int PRIMECOUNT = 28;static const size_t primeList[PRIMECOUNT] ={53ul, 97ul, 193ul, 389ul, 769ul,1543ul, 3079ul, 6151ul, 12289ul, 24593ul,49157ul, 98317ul, 196613ul, 393241ul, 786433ul,1572869ul, 3145739ul, 6291469ul, 12582917ul,25165843ul,50331653ul, 100663319ul, 201326611ul, 402653189ul,805306457ul,1610612741ul, 3221225473ul, 4294967291ul};size_t i = 0;for (; i < PRIMECOUNT; ++i){if (primeList[i] > prime)return primeList[i];}return primeList[i];
}

insert扩容时的逻辑就变成了size_t newsize = GetNextPrime(_table.size());
这样下来我们的哈希表就可以跑起来了。

完整代码

个人gitee

总结

unordered系列模拟实现的东西比较多,需要掌握,另外,其实哈希还有一个位图 、布隆过滤器就在下次讲了。

http://www.dtcms.com/a/297706.html

相关文章:

  • gig-gitignore工具实战开发(四):使用ai辅助生成gitignore
  • 开讲啦|MBSE公开课:第二集 MBSE远景设想
  • cocos creator 3.8.6 websocke的一直报错WebSocket is not a constructor
  • Logstash 多表增量同步 MySQL 到 Elasticsearch:支持逻辑删除与热加载,Docker 快速部署实战
  • vue项目创建流程
  • XML的简略知识点
  • PyCharm高效开发全攻略
  • RHCA - CL260 | Day01:Ceph 架构及环境介绍
  • leetcode102:二叉树的层序遍历(队列实现)
  • 栈----1.有效的括号
  • iOS WebView 调试实战,文件上传与权限弹窗异常的排查路径
  • 三维模型驱动下的光伏组件智能排列方案
  • 【深入底层】C++开发简历4+4技能描述6
  • 百特搭AI低代码平台助力企业国际化业务敏捷拓展
  • 《一种采用分布式多模态传感模块的身体尺度机器人皮肤的设计、评估与应用》论文解读
  • 【日志】unity俄罗斯方块——边界限制检测
  • 如何在离线电脑win11上安装VS2022
  • PI 思维升级 PI设计的典范转移:从阻抗思维到谐振控制
  • 在 Ansys CFX Pre 中配置 RGP 表的分步指南
  • C++中的反向迭代器
  • java面试题(中级题汇总)
  • k8s 中的 deployment,statefulset,daemonset 控制器的区别
  • 特征值与特征向量
  • 腾讯云CodeBuddy+微信小程序:5分钟开发番茄小闹钟
  • 电科金仓新一代数据库一体机:国产化方案替换优质选择
  • 本地内网IP映射到公网访问如何实现?内网端口映射外网工具有哪些?
  • python学习DAY22打卡
  • 深入解析Hadoop高频面试题:HDFS读/写流程的RPC调用链
  • yolov8通道级剪枝讲解(超详细思考版)
  • 解密负载均衡:如何轻松提升业务性能