数据结构 哈希搜索结构

39 篇文章 0 订阅

我们之前学过的查找方法有很多:
1.静态搜索
顺序查找:O(N)。
二分查找:O(logN)。
2.动态搜索
二叉搜索树:最优查询效率O(logN),最差查找效率O(N)。
AVL树:O(logN)。
但是上述的查找都是要经过元素比较才能进行查找的。查询的效率取决于比较的次数。

哈希结构

我们理想的搜索方法是:不进行元素比较,而是对每个元素的存储格式进行改造,通过某种方式,将元素与存储结构建立一一对应的关系。这样就可以通过这种关系快速地找到对应的元素。

插入时:
让插入的元素经过某些函数计算出它的插入位置,进行插入。
查找时:
先计算出此元素的位置,再根据位置找到结构这种对应的数,进行比较。

这种方式的结构就是哈希搜索结构,某些函数就是哈希函数,存储数据的表格为哈希表,计算出每个元素的位置位哈希表的下标

举个例子:
在这里插入图片描述

哈希冲突

那么我们如果按照上述哈希函数的计算方法,哈希地址(下标)肯定会有重复的。但是此时对应位置上已经有元素了,这就是哈希冲突。(一个哈希函数不论设计的有多好,都不可能避免哈希冲突,只能降低发生哈希冲突的概率。)

如何检测哈希函数是否合理呢?
1.保证哈希函数值域必须在表格范围内。 也就是通过哈希函数计算出来的下标,必须在哈希表下标范围内。
2.尽量保证哈希函数的值域均匀分布。
3.哈希函数尽可能简单。

那么我们如何来解决哈希冲突呢?
1.闭散列。

闭散列实现的方法是:如果发生哈希冲突,则往哈希表后面找空位置放入待插入数据。

这样的找空位置的方式有两种:
(1)线性探测
功能:从当前哈希地址开始,向后面寻找空位置放入待插入元素。(若到哈希表末尾,则从头看开始继续寻找)。
优点
处理哈希冲突的方式非常简单。
缺点
一旦发生哈希冲突,容易造成数据的堆积,也就是发生连续的冲突。例子:如果我们待插入的数组为{11,12,22,13,14,15,16},刚开始11和12都在自己的哈希地址上,但是从22开始,22占了13的哈希地址,13占了14的哈希地址,14占了15的哈希地址,这就导致连续的发生哈希冲突。)

这里大家可能会有疑问了,如果此时我们插入一个新元素,但是哈希表中没有空位置了,但是我们不知道一直在往后面寻找空位,那最终不就相当于把哈希表遍历了一遍,这和普通的查询算法不就没有区别了么?
这时我们既要引入负载因子这个概念。
负载因子=哈希表中实际占有空间的元素个数 / 哈希表的总大小

正常情况下哈希表是不会放满的,当负载因子达到70%的时候,哈希表就会自动扩容,所以也就不存在上述问题了。

(2)二次探测
因为线性探测是发生冲突时挨着向后找空位置,有可能造成数据堆积。所以我们不让其进行挨着找空位置,这样就不会产生问题,因此我们就用二次探测,那么二次探测怎么实现呢?
功能:不让其挨着向后找空位置,隔一段距离找。
原理:假设我们第一次得出的哈希地址为H0,若此位置为空,则正常插入元素。反之,则发生冲突,进行探测空位置。我们规定每次探测的哈希地址和第一次的哈希地址关系为 H(i)=H0+i^2这样就可以保证前一个哈希地址和后一个距离很远,解决了数据堆积的问题。

通过对式子的推导可以得H(i+1)=(H(i)+2*i+1)%哈希表的容量。

这里解释一下为什么要%哈希表的容量
因为我们前一个地址与后一个地址相距的比较远,后一个地址就有可能超出哈希表容量,此时需要%哈希表的容量来当做这次探测的地址。但是不能和线性探测一样到末尾直接从头开始探测,因为二次探测地址每次的跨度比较大,有可能这次探测地址越界,我们让其从头开始探测,因为式子中有i^2,所以通过计算下一次地址还可能会直接越界,又从头开始探测,因此没法正确的进行探测空位置。

优点
解决线性探测造成的数据堆积问题。
缺点
当表格中空位置较少,找到下一个空位置可能要探测很多次。

研究表明:当表的长度为质数且表负载因子不超过0.6时,新的表项一定能够插入,而且任何一个位置都不会被探查两次。

闭散列的扩容方式:
1.创建一个新的哈希表。
2.将旧哈希表中状态为存在的元素(删除状态的元素没必要了),向新哈希表中插入。

必须要调用新哈希表的insert()函数,因为insert()函数里有哈希函数,当容量变了,相应的通过哈希函数得出的哈希地址也会改变。如果按照vector的扩容方式,直接将旧哈希表的数据,按照对应哈希地址拷贝到新的哈希表中,就会造成在新的哈希地址中找不到原来的元素。

闭散列以及实现扩容的代码:
闭散列代码

2.开散列。

原理:哈希函数与之前是相同的。当发生冲突时,我们将冲突的元素放在一个集合里,这个集合称为,在集合里各个冲突的元素是通过链表链接起来的,所以哈希表中的元素是各个桶的头结点**。我们把以开散列解决冲突的哈希称为哈希桶**
用一张图来理解一下:
在这里插入图片描述
闭散列扩容方式我们知道,那么哈希桶的扩容方式是什么呢?
哈希桶扩容方式:
1.创建一个更大的哈希桶的对象NewHash。
2.将旧哈希桶中的结点搬移到NewHash中。
3.将NewHash和旧哈希桶的哈希表和容量进行交换。
4.销毁NewHash。

哈希桶实现代码:
哈希桶实现

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值