C++数据结构之哈希表如何实现


本篇内容主要讲解“C++数据结构之哈希表如何实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C++数据结构之哈希表如何实现”吧!二叉搜索树具有对数时间的表现,但这样的表现建立在一个假设上:输入的数据有足够的随机性。哈希表又名散列表,在插入、删除、搜索等操作上具有「常数平均时间」的表现,而且这种表现是以统计为基础,不需依赖输入元素的随机性。听起来似乎不可能,倒也不是,例如:假设所有元素都是 8-bits 的正整数,范围 0~255,那么简单得使用一个数组就可以满足上述要求。首先配置一个数组 Q,拥有 256 个元素,索引号码 0~255,初始值全部为 0。每一个元素值代表相应的元素的出现次数。如果插入元素 i,就执行 Q[i]++,如果删除元素 i,就执行 Q[i]–,如果查找元素 i,就看 Q[i] 是否为 0。这个方法有两个很严重的问题。如果元素是 32-bits,数组的大小就是232=4GB,这就太大了,更不用说 64-bits 的数了如果元素类型是字符串而非整数,就需要某种方法,使其可用作数组的索引如何避免使用一个太大的数组,以及如何将字符串转化为数组的索引呢?一种常见的方法就是使用某种映射函数,将某一元素映射为一个「大小可接受的索引」,这样的函数称为散列函数。散列函数应有以下特性:函数的定义域必须包含需要存储的全部关键字,当散列表有 m 个地址时,其值域在 0 到 m – 1 之间函数计算出来的地址能均匀分布在整个空间取关键字的某个线性函数为散列地址:Hash(Key)=A∗Key+B优点:简单、均匀缺点:需要事先知道关键字的分布情况使用场景:数据范围比较集中的情况设散列表的索引个数为 m,取一个不大于 m,但最接近 m 的质数 p 最为除数,按照散列函数:Hash(Key)=key,将关键字转化为哈希地址假设关键字为 1230,它的平方是 1512900,取中间的 3 位 129 作为哈希地址;再比如关键字为 321,它的平方是 103041,取中间的 3 位 304(或 30)作为哈希地址。使用散列函数会带来一个问题:可能有不同的元素被映射到相同的位置。这无法避免,因为元素个数大于数组的容量,这便是「哈希冲突」。解决冲突问题的方法有很有,包括线性探测、二次探测、开散列等。当散列函数计算出某个元素的插入位置,而该位置上已有其他元素了。最简单的方法就是向下一一寻找(到达尾端,就从头开始找),直到找到一个可用位置。进行元素搜索时同理,如果散列函数计算出来的位置上的元素值与目标不符,就向下一一寻找,直到找到目标值或遇到空。至于元素的删除,必须采用伪删除,即只标记删除记号,实际删除操作在哈希表重新整理时再进行。这是因为哈希表中的每一个元素不仅表示它自己,也影响到其他元素的位置。从上述插入过程我们可以看出,当哈希表中元素变多时,发生冲突的概率也变大了。由此,我们引出哈希表一个重要概念:负载因子。负载因子定义为:Q = 表中元素个数 / 哈希表的长度负载因子越大,剩余可用空间越少,发生冲突可能越大负载因子越小,剩余可用空间越多,发生冲突可能越小,同时空间浪费更多因此,控制负载因子是个非常重要的事。对于开放定址法(发生了冲突,就找下一个可用位置),负载因子应控制在 0.7~0.8 以下。超过 0.8,查找时的 CP免费云主机域名U 缓存不命中按照指数曲线上升。线性探测的缺陷是产生冲突的数据会堆在一起,这与其找下一个空位置的方式有关,它找空位置的方式是挨着往后逐个去找。二次探测主要用来解决数据堆积的问题,其命名由来是因为解决碰撞问题的方程式F(i)=i2是个二次方程式。更具体地说,如果散列函数计算出新元素的位置为 H,而该位置实际已被使用,那么将尝试H+12,H+22,H+32,…,H+i2,而不是像线性探测那样依次尝试H+1,H+2,H+3,…,H+i。大量实验表明:当表格大小为质数,而且保持负载因子在 0.5 以下(超过 0.5 就重新配置),那么就可以确定每插入一个新元素所需要的探测次数不超过 2。这种方法是在每一个表格元素中维护一个链表,在呢个链表上执行元素的插入、查询、删除等操作。这时表格内的每个单元不再只有一个节点,而可能有多个节点。节点的定义:接口总览节点的结构因为在闭散列的哈希表中的每一个元素不仅表示它自己,也影响到其他元素的位置。所以要使用伪删除,我们使用一个变量来表示。哈希表的节点结构,不仅存储数据,还存储状态。查找查找的思路比较简单:利用散列函数获取映射后的索引遍历数组看是否存在,直到遇到空表示查找失败在上面代码的查找过程中,加了句用于判断是否重复查找的代码。理论上上述代码不会出现所有的位置都有数据,查找不存在的数据陷入死循环的情况,因为哈希表会扩容,闭散列下负载因子不会到 1。但假如,我们插入了 5 个数据,又删除了它们,之后又插入了 5 个数据,将 10 个初始位置都变为非 EMPTY。此时我们查找的值不存在的话,是会陷入死循环的。插入插入的过程稍微复杂一些:1.首先检查待插入的 key 值是否存在2.其次需要检查是否需要扩容3.使用线性探测方式将节点插入删除删除的过程非常简单:1.查找指定 key2.找到了就将其状态设为 DELETE,并减少表中元素个数接口总览节点的结构使用链地址法解决哈希冲突就不再需要伪删除了,但需要一个指针,指向相同索引的下一个节点。查找查找的实现比较简单:1.利用散列函数获取映射后的索引2.遍历该索引位置的链表插入开散列下的插入比闭散列简单:1.首先检查待插入的 key 值是否存在2.其次需要检查是否需要扩容3.将新节点以头插方式插入删除开散列的删除与闭散列有些许不同:1.获取 key 对应的索引2.遍历该位置链表,找到就删除到此,相信大家对“C++数据结构之哈希表如何实现”有了更深的了解,不妨来实际操作一番吧!这里是百云主机网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

相关推荐: es6工厂模式怎么实现

本篇内容介绍了“es6工厂模式怎么实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成! es6有工厂模式;工厂模式将逻辑封装到一个函数中,在es6中可以不使用构造…

免责声明:本站发布的图片视频文字,以转载和分享为主,文章观点不代表本站立场,本站不承担相关法律责任;如果涉及侵权请联系邮箱:360163164@qq.com举报,并提供相关证据,经查实将立刻删除涉嫌侵权内容。

(0)
打赏 微信扫一扫 微信扫一扫
上一篇 05/21 11:31
下一篇 05/21 11:31

相关推荐