最新要闻
- RTX 4080为何要定价这般高?背后原因揭开
- 【环球热闻】面对矿卡 老黄已经悄悄带头冲锋了!一箭双雕 真是绝了
- 环球短讯!旧版微博拜拜:大量用户被直接改为新版界面
- 全球新动态:工信部出手!手机预装App终于能卸载了:2023年执行
- 切勿模仿!男子让幼儿握方向盘开车还拍摄炫耀:扣3分、罚200
- 4G已够用 美国运营商推5G陷入麻烦:4200亿投资难赚回来
- 全球简讯:Intel Arc显卡驱动升级:吃鸡快了4%、还有16个Bug
- 百事通!玩家最担心的事要发生!AMD RX 7900系列大概率涨价
- 天天微资讯!CDPR确认《巫师3》次世代版存在Bug:将尽快修复
- 高玩一步到位 阿斯加特32GB DDR5-6800 RGB灯条1350元
- 世界快资讯丨《流浪地球2》公布星尘海报:人类的勇气永刻星空
- 全球快播:原因不服不行 大巴黎官方:我们已锁定世界杯冠军!梅西/姆巴佩争金靴
- 世界热讯:《死侍3》确认为R级!休·杰克曼将回归饰演金刚狼
- 环球快报:奇瑞SUV颜值天花板!俄罗斯花滑“千金”喜提OMODA C5:定制车身
- 微头条丨工资加倍都招不到人!官方要求北京快递业人员应返岗尽返岗
- 最强护眼屏!moto X40蓝光占比远低于行业均值
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
全球观点:哈希表总结
哈希表
理论讲解
元素通过确定的映射关系找到其在表中的存储位置,这个映射关系叫做哈希函数(散列函数),==这张表就叫做哈希表(散列表)
(资料图片)
优势:适用于快速的查找,时间复杂度为O(1)
缺点:占用内存空间比较大,哈希表的空间效率还是不够高。链式哈希表,每个桶上存一个链表,而链表的每一个节点除了数据域外,还有地址域,1个地址要占4字节(8字节),如果数据规模很大的话,那么内存的占用量就会非常大,甚至无法进行完全存储。
无序关联容器unordered_set、unordered_map的底层实现就是哈希表
哈希函数
设计特点:
- 计算简单(太复杂会降低查询时间)
- 散列地址分布均匀(减少哈希冲突发生概率)
哈希函数:
- 除留余数法
- 数字分析法
- 平方取中法
- md5,sha加密hash算法等
哈希冲突(哈希碰撞)
元素通过哈希函数映射到同一个地址,就表示发生了哈希冲突
发生哈希冲突时的解决方法:
- 线性探测法 (开放定址法)
- 当前地址发生冲突了,就往后继续找空闲的位置
- 缺陷:当表中的元素越来越多时,线性探测法为了找到空闲位置需要遍历的长度就会越来越长,性能由O(1)逐渐达到O(n)
- 二次探测法:是对线性探测法的改进
- 链地址法
尽可能减少发生哈希冲突的概率:
- 哈希函数是除留余数法时,将哈希表的长度设为素数,确保计算后的哈希地址能够尽可能离散
- 哈希表的装载因子 loadfactor
已占用的桶的个数 / 桶的总个数 > loadfactor
时,哈希表就要进行扩容了,扩容时原来哈希表中的元素需要重新进行哈希,代价是比较大的, 时间复杂度达到O(n)
线性探测哈希表实现
分析
每个位置的状态:
正在使用
从未使用
当前位置的元素被删除
添加元素
扩容操作:如果 (已占用的桶的个数 / 桶的总个数 > loadfactor)则需要先进行扩容!!
通过哈希函数计算数据存放的位置(哈希地址)
如果该地址是空闲的,直接存储元素,完成
如果该位置被占用(发生冲突了),那么采用线程探测法继续往后找空闲的位置来存放元素
查询操作
通过哈希函数计算数据存放的位置,从该位置取值(需要判断该位置的状态是否为正在使用中)
如果取出的值 == 要查询的元素值,查询成功
如果取出的值 != 要查询的元素值(之前往这个位置放元素时,发生了哈希冲突),继续往后遍历查找该元素
删除操作
过哈希函数计算数据存放的位置,从该位置取值,判断状态是否为正在使用中
如果取出的值 == 要删除的值,直接修改当前位置的状态即可(置为已删除状态)
如果取出的值 != 要删除的值,继续往后遍历查找该元素,如果遇到某个位置为从未使用的状态,那么就说明哈希表中没有该元素
关于素数表
我们的哈希函数采用除留余数法,为了让地址更离散,表的长度设置为素数,我们能够通过程序得到固定范围内的所有素数(只能被1和本身整除),但是并不是每次扩容都是紧挨着进行扩容的,因为扩容的开销比较大,所以每次扩容的时候都扩大一点,素数的选择最好比较离散些。
可以参考该素数表:[3, 7, 23, 47, 97, 251, 443, 911, 1471, 42773]
实现
#include using namespace std;// 桶状态enum State{STATE_USEING,STATE_UNUSED,//从未被用过STATE_DEL,//用过但是已经删除};//桶结构struct Bucket {Bucket(int key=0, State state = STATE_UNUSED):key_(key),state_(state){}int key_;//桶存放的数据State state_;//桶的状态信息};// 定义哈希表class HashTable {public:HashTable(int size=primeTable_[0], double loadfactor=0.75) :tableSize_(size),useBucketNum_(0),loadfactor_(loadfactor),primeIdx_(0){//我们要保证保证表长是素数(从线性表里取),所以需要对传入的size进行调整int i = 0;for (; i < PRIME_SIZE; i++) {if (primeTable_[i] >= size) {tableSize_ = primeTable_[i];primeIdx_ = i;break;}}//传入的值过大,可以调整到最后一个素数if (i == PRIME_SIZE){primeIdx_--;}pBucket_ = new Bucket[tableSize_];}~HashTable() {delete[]pBucket_;pBucket_ = nullptr;}// 增加元素操作bool insert(int val) {//判断是否是要扩容double factor = useBucketNum_ * 1.0 / tableSize_;cout << "factor:" << factor << endl;if (factor >= loadfactor_) {expand();//按照素数表进行扩容}// 采用除留余数法找到元素在哈希表中对应的位置int idx = val % tableSize_;int k = idx;do {if (pBucket_[k].state_ != STATE_USEING) {pBucket_[k].state_ = STATE_USEING;pBucket_[k].key_ = val;useBucketNum_++;return true;}k = (k + 1) % tableSize_;} while (k != idx);//k遍历了一边后(实际上由于设置了装载因子并不会出现遍历一边的情况)还无法找到插入位置,则插入失败return false;}// 查询操作bool find(int val) {int idx = val % tableSize_;int k = idx;do {if (pBucket_[k].state_ == STATE_USEING && pBucket_[k].key_ == val) {return true;}k = (k + 1) % tableSize_;} while (pBucket_[k].state_==STATE_UNUSED && k != idx);return false;}// 删除操作bool erase(int val) {if (useBucketNum_ == 0)throw "哈希表为空,没有可删除元素!";int idx = val % tableSize_;int k = idx;do {if (pBucket_[k].state_ == STATE_USEING && pBucket_[k].key_ == val) {pBucket_[k].state_ = STATE_DEL;useBucketNum_--;}k = (k + 1) % tableSize_;} while (pBucket_[k].state_ == STATE_UNUSED && k != idx);return true;}private:void expand() {//按照素数表进行扩容if (primeIdx_+1 == PRIME_SIZE)throw "the HashTable is too large,expand false!";primeIdx_++;Bucket* p = new Bucket[primeTable_[primeIdx_]];// 遍历原哈希表中的元素,并对它们进行重新哈希for (int i = 0; i < tableSize_; i++) {if (pBucket_[i].state_ == STATE_USEING) {// 用新表长哈希int idx = pBucket_[i].key_% primeTable_[primeIdx_];int k = idx;do {if (p[k].state_ != STATE_USEING) {// 不要再useBucketNum_++,因为已用桶的数量没有变p[k].state_ = STATE_USEING;// 注意修改状态p[k].key_ = pBucket_[i].key_;break;}//线性探测用的也是新表长k = (k + 1) % primeTable_[primeIdx_];} while (k != idx);}}tableSize_ = primeTable_[primeIdx_];delete[]pBucket_;pBucket_ = p;}Bucket* pBucket_;int tableSize_;//哈希表长度int useBucketNum_;//已用桶的数目double loadfactor_;//装载因子,用于扩容//素数表用于扩容//所有哈希表对象都使用同一个素数表//c++11中对静态整形常量可以在类内部初始化static const int PRIME_SIZE = 10;//素数表大小static int primeTable_[PRIME_SIZE];int primeIdx_;//素数表索引,不同对象的索引值可能不同};int HashTable::primeTable_[PRIME_SIZE] = { 3,7,23,47,97,251,443,911,1471,42773 };
测试代码
int main(){HashTable htable;htable.insert(21);htable.insert(32);htable.insert(14);htable.insert(15);htable.insert(22);cout << htable.find(21) << endl;htable.erase(21);cout << htable.find(21) << endl;}
链式哈希表实现
线性探测哈希表的缺陷:
- 发生哈希冲突时,采用线性探测法,时间复杂度会逐渐靠近O(n)
- 多线程环境中,线性探测所用到的基于数组实现的哈希表,只能给全局的表加互斥锁来保证对哈希表操作的原子性,保证线程安全!
分析
- 链式哈希表中每个桶都放一个链表,把发生哈希冲突的元素都挂到对应的桶上
- 哈希函数采用除留余数法,表长为素数
- 优化策略:
- 如果每个桶的链表比较长,那么链表搜索花费的时间就会比较长,时间复杂度会趋于O(1) ,这时可以考虑把链表转化成红黑树,红黑树的查找时间复杂度达到O(logn)
- 在多线程环境下,可以为链式哈希表的每个桶创建自己的分段锁,不同桶中的链表操作是可以并发执行的!
实现
#include #include #include #include using namespace std;class HashTable {public:HashTable(int size = primeTable_[0], double loadfactor = 0.75) :useBucketNum_(0),loadfactor_(loadfactor),primeIdx_(0){// 需要将哈希表的长度调整到素数长度for (; primeIdx_ < PRIME_SIZE; primeIdx_++) {if (primeTable_[primeIdx_] >= size) {table_.resize(primeTable_[primeIdx_]);break;}}if (primeIdx_ == PRIME_SIZE) {table_.resize(primeTable_[--primeIdx_]);}}// 不需要再自定义析构函数了,vector成员变量会调用它的析构函数来释放占用的外部内存void insert(int val) {// 1.插入前先判断需不需要扩容// 先查看当前的装载程度double factor = (useBucketNum_ * 1.0) / primeTable_[primeIdx_];cout << "(当前装载程度)factor:" << factor << endl;if (factor >= loadfactor_) {expand();}// 利用除留余数法(哈希函数)找到桶索引int idx = val % primeTable_[primeIdx_];if (table_[idx].empty()) {// 仅当桶中的链表容器为空,要插入第一个元素时useBucketNum_++useBucketNum_++;// 头插法插入第一个元素table_[idx].emplace_front(val);}else {// 去重操作-->或者说如果元素已经存在就不再进行插入操作// 使用C++算法库提供的泛形函数auto it = ::find(table_[idx].begin(), table_[idx].end(), val);if (it == table_[idx].end()) // {// 要插入的是新元素table_[idx].emplace_front(val);}}}bool erase(int val) {int idx = val % primeTable_[primeIdx_];auto it = ::find(table_[idx].begin(), table_[idx].end(), val);if (it != table_[idx].end()) {table_[idx].erase(it);// 删除后需要判断当前桶是否为空,空的话需要useBucketNum_--if (table_[idx].empty()) {useBucketNum_--;}return true;}return false;}bool find(int val) {int idx = val % primeTable_[primeIdx_];auto it = ::find(table_[idx].begin(), table_[idx].end(), val);if (it != table_[idx].end()){ return true;}return false;}private:void expand() {// 先判断是否允许再扩容if (primeIdx_ + 1 == PRIME_SIZE)throw "the HashTable is too large!";primeIdx_++;// 千万注意,由于扩容后需要重新哈希,所以链表的分布极有可能发生变化,所以要将useBucketNum_=0,重新记录!!!useBucketNum_ = 0;vector> oldTable;// 发生交换后,table_就是一张空表了table_.swap(oldTable);table_.resize(primeTable_[primeIdx_]);// 把原来表中的每个元素重新哈希到扩容后的表中for (auto list : oldTable) {for (auto key : list) {int idx = key % primeTable_[primeIdx_];if (table_[idx].empty()) {useBucketNum_++;}table_[idx].emplace_front(key);}}}vector> table_;// 哈希表int useBucketNum_;// 已用桶的数目double loadfactor_;// 装载因子static const int PRIME_SIZE = 10;static int primeTable_[PRIME_SIZE];int primeIdx_;};int HashTable::primeTable_[PRIME_SIZE] = { 3,7,23,47,97,251,443,911,1471,42773 };
测试代码
int main() {HashTable htable;htable.insert(21);htable.insert(32);htable.insert(14);htable.insert(15);htable.insert(22);htable.insert(23);cout << htable.find(21) << endl;htable.erase(21);cout << htable.find(21) << endl;return 0;}
关于代码中的table_.swap()
操作,这个操作的效率会不会比较低?
实际上,这里两个容器只是把成员变量做了一个交换,也就是底层指针的指向做了交换,并没有涉及内存的构造,拷贝,所以效率是很高的
**注意: **
- 如果两个容器使用的空间配置器allocator是一样的,即拥有相同的内存管理方式,那么可以使用swap操作,成员变量直接交换,效率高!
- 如果两个容器使用的空间配置器allocator是不一样的,即拥有不同的内存管理方式,那么就需要采用效率低的整个数据的交换
总结
全球观点:哈希表总结
当前报道:2022 ICPC 杭州站 K - Master of Both // Trie
RTX 4080为何要定价这般高?背后原因揭开
【环球热闻】面对矿卡 老黄已经悄悄带头冲锋了!一箭双雕 真是绝了
环球短讯!旧版微博拜拜:大量用户被直接改为新版界面
全球新动态:工信部出手!手机预装App终于能卸载了:2023年执行
切勿模仿!男子让幼儿握方向盘开车还拍摄炫耀:扣3分、罚200
4G已够用 美国运营商推5G陷入麻烦:4200亿投资难赚回来
全球简讯:Intel Arc显卡驱动升级:吃鸡快了4%、还有16个Bug
百事通!玩家最担心的事要发生!AMD RX 7900系列大概率涨价
天天微资讯!CDPR确认《巫师3》次世代版存在Bug:将尽快修复
高玩一步到位 阿斯加特32GB DDR5-6800 RGB灯条1350元
精选!浅析JWT Attack
记录--uniapp 应用APP跳转微信小程序
天天滚动:用 ChatGPT 来完成笔试题
MYSQL 3 DAY
世界热点评!第一百一十三篇: JS数组Array(二)数组方法 栈、队列、排序
世界快资讯丨《流浪地球2》公布星尘海报:人类的勇气永刻星空
全球快播:原因不服不行 大巴黎官方:我们已锁定世界杯冠军!梅西/姆巴佩争金靴
世界热讯:《死侍3》确认为R级!休·杰克曼将回归饰演金刚狼
环球快报:奇瑞SUV颜值天花板!俄罗斯花滑“千金”喜提OMODA C5:定制车身
微头条丨工资加倍都招不到人!官方要求北京快递业人员应返岗尽返岗
环球时讯:行业方案 | 新规落地,企业集团财务公司如何构建数智财务体系?
环球实时:模板层之标签 自定义模板语法 模板的继承与导入 搭建测试环境 ORM常用关键字
最强护眼屏!moto X40蓝光占比远低于行业均值
当前速看:为了让人多下游戏?特斯拉推出1TB车规级固态硬盘
世界实时:奥迪新车开了半小时咚咚响 4S店换零件车主想换车
当前看点!5年研发投入1000亿!小米发布首部知识产权白皮书:授权专利超2.9万项
每日速读!Redmi K60要用上陶瓷/素皮了?卢伟冰在线征集偏好:陶瓷第一
微头条丨Prometheus技术分享——如何监控宿主机和容器
当前视点!人类核聚变取得突破性进展:什么是核聚变、重要吗?
【热闻】闷声发大财 奇瑞第四代混动专用1.5L发动机下线:油耗大降
世界微头条丨7单元发声设计!小米Sound Pro智能音箱开售 首发999元
世界最新:小米13 Pro绝配!小米50W立式风冷无线充Pro图赏
全球热议:跑分突破133万!努比亚Z50《原神》半小时稳成直线
天天关注:学习 Shell准没错
天天速讯:Python3 编程面试题
焦点讯息:快递代拿项目 (第十组)终稿
焦点速看:面试题:浏览器输入 URL 后回车发生了什么?
破坏系统是为了更稳定?混沌工程在去哪儿的 4 个阶段实践
世界观点:产品分享:Qt鸿图电子智慧白板(适合会议机、电子黑板、电子笔记、电子阅读器等场景),当前版本v1.0.0
win7游戏不能全屏怎么解决?win7游戏不能全屏解决方法有哪些?
酷狗可以下载歌词吗?酷狗怎么下载歌词?
itunes怎么制作铃声?itunes备份文件在哪里?
chrome是什么浏览器?chrome文件夹可以删除吗?
在Excel中如何排序?excel中身份证号码怎么全部显示?
天天快看:Visual Studio下创建MFC项目,并结合OpenGL实现一个小程序
环球新动态:Go适合做什么?为何这么多人偏爱Go语言?
快消息!【脚本项目源码】Python制作艺术签名生成器,打造专属你的个人艺术签名
北桥温度高的原因有哪些?北桥温度高有什么影响?
手机通话清单怎么查询?手机通话清单怎么清除?
支付宝沾沾卡怎么获得?支付宝沾沾卡怎么使用?
华为路由a1是千兆吗?华为路由a1怎么重新设置?
微信故障是什么原因?微信故障怎么修复?
脑筋急转弯什么人不怕冷?脑筋急转弯什么狗不会叫的5种答案是什么?
经常请吃饭的漂亮姐姐插曲有哪些?经常请吃饭的漂亮姐姐剧情介绍
x战警范冰冰扮演的是什么角色?x战警范冰冰是哪一部
前端跨域
MySQL 行溢出
springboot+vue 若依项目在windows2008R2企业版部署流程
环球视点!SpringCloud-Nacos学习笔记
铝合金铸造工艺有哪些?铝合金铸造工艺流程
苦主是什么意思?苦主引申含义是什么?
世界微速讯:上网认证(锐捷睿易篇)
当前快看:JNPF3.4.5消息模块:多渠道应用,配置灵活多样,满足更多使用场景
【环球热闻】基于汉兰达开发而来 雷克萨斯TX效果图曝光:竟与奇瑞星途“撞衫”
微软正式放弃Win10 21H1!将无法收到任何安全更新或补丁
亚米级的高精度定位 高德北斗卫星日定位量已超2100亿次
不枉马粉苦等一场 全新马自达CX-90预告:六缸、后驱全都有
【全球快播报】对标迈巴赫S级!蔚来百万级豪车计划落地 售价百万
SAP根据源码导入/ui2/cl_json类
Docker 安装,常用命令
【当前独家】告别LCD/mini LED iPad Pro全系升级到OLED屏
全球快资讯:高端成了!小米12S Ultra被中国移动评为4000元以上最强旗舰
视点!果粉霸气!花万元同时入手小米13和13 Pro:之前用的是iPhone 14 Pro Max
火箭平民化!中国民营火箭朱雀二号即将首飞:人类首次挑战甲烷燃料
小米13系列刷新认知 卢伟冰6字评价:彻底脱胎换骨
快讯:3999元起!小米13今日开售:手感、续航碾压iPhone 14 Pro
消息称苹果要对iOS开放 iPhone等自由了:功能、应用商店向第三方放开
苹果推送iOS 16.2正式版:新增无边记、Apple Music唱歌
阿根廷3-0克罗地亚晋级决赛!梅西创纪录之夜:成现役世界杯射手王
后退N帧协议(GBR)
环球新资讯:taro 编译报:模块引入顺序不一致报错
环球微动态丨主持人邀请世界首富马斯克登台后 现场嘘声一片:尴尬到家
前沿资讯!科幻美剧《西部世界》凉凉!将被彻底下线:美国都不能播了
播报:美国宣布首次实现“核聚变点火”!终于不再“赔本”了
短讯!RX 7900 XT/XTX首发开卖几分钟告罄!黄牛炒疯了:两倍溢价
每日观点:阿根廷时隔四年再战克罗地亚!半决赛现场将播放陈奕迅《孤勇者》
焦点讯息:前端入门教程:CSS标准盒模型和怪异盒模型区别
世界新资讯:卡梅隆力荐!《阿凡达:水之道》CINITY版明日点映:我国自主研发
啥?青岛海边能捡到帝王蟹引围观 网友称赚大 专家解答:不是帝王蟹
世界短讯!神舟十四号航天员摄影作品展:16个地方你认识多少?
《三体》动画爆火 “三体宇宙”能成中国版“漫威宇宙”吗?
每日短讯:Django框架:3、Django请求生命周期(重要)
环球微头条丨全年零事故率!换换智能换电解决电动车最大安全隐患
暴雪与新代理展开洽谈 魔兽等国服谁接?网易偷笑 新版号这难题无解
当前热文:一加11R参数曝光:6.7寸120Hz屏、搭载红外传感器
1*5 句话月考游寄
快资讯:女子下班回家发现2千万豪宅“塌了”:科普何为毛细管网
国际乒联服务器出问题 马龙、樊振东等信息遭泄漏