C++---哈希闭散列与开散列

产生原因

在顺序结构或树形结构的数据集合中,我们想要查询一个元素时,必须进行遍历,所以顺序结构的查询时间复杂度为O(N),树形结构查询的时间复杂度为O(log2^N),但是我们想要一种不用遍历就知道其位置的方法,我们在查询时就会很方便。

哈希

哈希也叫作映射,我们可以通过其元素的值(关键码),查找到该元素的具体位置。我们将这种一一对应的称为映射。==哈希是一种存储结构。==通过这种映射方式构造出来的结构称为散列表。

举一个简单栗子:一次函数。
在这里插入图片描述
我们知道x值,通过函数表达式,我们就可以计算出y的值。这种一一对应的关系就是映射,这个函数表达式就是哈希函数。

  • 插入元素:根据插入的元素(关键码),通过哈希函数计算出存储位置,将元素插入到指定位置。
  • 查询元素:将该元素通过哈希函数计算出相应的位置,我们在将该位置的元素取出,判断值是否相等,如果相等则表示存在。

在这里插入图片描述
上述的方法确实能够实现查询的时间复杂度为O(1),但是也存在一个问题,如果我们插入的元素是2,22,32,222呢?我们通过哈希函数计算的位置相同,这就是哈希冲突。

哈希冲突

不同的元素通过哈希函数计算出来的位置相同,我们称其为哈希冲突或者哈希碰撞。
产生哈希冲突的原因之一:哈希函数设计缺陷:

  • 哈希函数设计的方法:
  1. 直接定值法:就类似于线性函数 y = kx+b的形式。
  2. 除留余数法:类似于上述栗子中的方法。
  3. 平方取中法:取一个数的平方中间几位数。
  4. 数学分析法:找一些数的规律,利用规律设计哈希函数。

哈希函数的设计是有效的预防哈希冲突,但是不能避免产生哈希冲突。如何避免哈希冲突:闭散列、开散列。

闭散列

开放定值法,当发生哈希冲突时,如果哈希表没有装满,说明在哈希表中必然还有空位置,那么我们可以将元素放到冲突的下一个位置。如何寻找下一个位置。

  • 线性探测:从发生冲突的位置开始,依次向后探测,直到找到下一个空位置为止。
    • 线性探测插入方法:通过哈希函数判断插入的位置。如果该位置没有元素则直接插入新元素,如果该位置发生哈希冲突,则使用线性探测找到写一个位置。
    • 线性探测删除方法:在线性探测不能随便删除一个元素,因为直接删除会影响到其他元素的搜索,所以删除采用的是伪删除。
    • 扩容时机
      负载因子=当有效元素的个数/闭散列的长度。当负载因子大于0.7时,需要进行扩容。
    • 缺点:
      容易产生线性堆积,导致查询效率降低。

为了解决线性探测所产生的线性堆积问题,我们避免产生数据堆积在一起,我们提出二次探测。

  • 二次探测:探测方式:Hi = (Ho + i^2)%capacity,i取值为1,2,3, ……
    • 扩容机制:
      当负载因子大于0.5,就需要考虑扩容。

闭散列就是用空间换时间的思想,提高查询效率

开散列

开散列称为链地址法,将通过哈希函数计算出具有相同地址空间的元素放在同一个子集中,这个子集我们称为哈希桶,桶中的元素通过单链表的链接起来。但是单链表插入分为头插与尾插,这里采用头插法进行讲解。
在这里插入图片描述
我们可以看出,每一个哈希桶中都存放的是产生哈希冲突的元素。

  • 扩容时机:当所有桶中元素的个数等于容量时,哈希表扩容。

开散列中增加链表指针,看似比闭散列中多了一个内存开销,但是由于闭散列中存在二次探测,二次探测的负载因子在达到0.5就需要进行扩容,总和来看,开散列更加节省空间

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值