数据结构 哈希

构造哈希函数的方法

构造哈希函数的原则是: 1. 函数本身便于计算;2. 计算出来的地址分布均匀,即对任一关键字k,f(k) 对应不同地址的概率相等。

  1. 直接定址法
    取关键字或者关键字的某个线性函数为Hash地址,即Hash(key)=a*key+b
  2. 除留余数法:
    假设哈希表长为m,p为小于等于m的最大素数,则哈希函数为h(k) = k % p。
  3. 平方取中法:
    先求出关键字的平方值,然后按需取平方值的中间几位作为哈希地址。这是因为:平方后中间几位和关键字中每一位都相关,故不同关键字会以较高的概率产生不同的哈希地址。

解决哈希冲突的方法

开放定址法

当一个关键字和另一个关键字发生冲突时,使用某种探测技术在Hash表中形成一个探测序列,然后沿着这个探测序列依次查找下去,当碰到一个空的单元时,则插入其中。基本公式为:hash(key) = (hash(key) + di) mod TableSize。其中di为增量序列,TableSize为表长。根据 di 的不同我们又可以分为线性探测,平方(二次)探测,双散列探测。

1)线性探测以增量序列 1,2,……,(TableSize -1)循环试探下一个存储地址,即di = i。如果table[index+di]为空则进行插入,反之试探下一个增量。但是线性探测也有弊端,就是会造成元素聚集现象,降低查找效率。

2)平方(二次)探测以增量序列1,-1,4,-4…且 q ≤ TableSize/2 循环试探下一个存储地址。

3)双散列探测:略

4)伪随机探测:略

特别对于开放定址法的删除操作,不能简单的进行物理删除,因为对于同义词来说,这个地址可能在其查找路径上,若物理删除的话,会中断查找路径,故只能设置删除标志

链地址法

又称拉链法、开散列,首先将关键码根据哈希函数来计算出哈希地址,对相同的哈希地址放在某一子集合中,每个子集合叫做一个桶,每个通放的都是哈希冲突元素,每个桶中的元素通过单链表连接,每个链表的头节点存放在哈希表中,这种结构叫做哈希桶

HashMap,HashSet 其实都是采用的拉链法来解决哈希冲突的,就是在每个位桶实现的时候,我们采用链表(jdk1.8之后采用链表+红黑树)的数据结构来去存取发生哈希冲突的输入域的关键字(也就是被哈希函数映射到同一个位桶上的关键字)。

使用拉链法解决哈希冲突的几个操作:

①插入操作:在发生哈希冲突的时候,输入域的关键字去映射到位桶(实际上是实现位桶的这个数据结构,链表或者红黑树)中去的时候,我们先检查带插入元素 x 是否出现在表中,很明显,这个查找所用的次数不会超过装载因子(n / m,n为输入域的关键字个数,m为位桶的数目),它是个常数,所以插入操作的最坏时间复杂度为O(1)。

②查询操作:和①一样,在发生哈希冲突的时候,检索的时间复杂度不会超过装载因子,检索数据的时间复杂度也是O(1)。

③删除操作:如果在拉链法中我们想要使用链表这种数据结构来实现位桶,那么这个链表一定是双向链表,因为在删除一个元素x的时候,需要更改 x 的前驱元素的 next 指针的属性,把 x 从链表中删除。这个操作的时间复杂度也是O(1)。

优点

与开放定址法相比,拉链法有如下几个优点:

①拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;

②由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;

③开放定址法为减少冲突,要求装填因子 α 较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α ≥ 1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;

④在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。

缺点

指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度。

再散列(双重散列,多重散列)

同时构造多个不同的哈希函数,当发生冲突时,使用第二个、第三个哈希函数计算地址,直到无冲突。

缺点:计算时间增加。

建立一个公共溢出区

将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表。

发布了54 篇原创文章 · 获赞 120 · 访问量 9518

猜你喜欢

转载自blog.csdn.net/siriusol/article/details/105346638
今日推荐