精品刷题路线参考:

https://github.com/youngyangyang04/leetcode-master

https://github.com/chefyuan/algorithm-base

哈希表题目

哈希表基础

哈希表也叫散列表,哈希表是一种映射型的数据结构。

哈希表是根据关键码的值而直接进行访问的数据结构。

就好像老三和老三的工位:有人来找老三,前台小姐姐一指,那个像狗窝一样的就是老三的工位。

总体来说,散列表由两个要素构成:桶数组与散列函数。

桶及桶数组

散列表使用的桶数组(Bucket array ),其实就是一个容量为 N 的普通数组,只不过在这里,我们将其中的每个单元都想象为一个“桶”(Bucket),每个桶单元里都可以存放一个条目。

比如,所有的关键码都是整数,我们就可以直接将 key 为关键码的那个条目存放在桶单元 A[key]内;为了节省空间,空闲的单元都被置为 null。

例如,吴零、熊大、王二、张三、李四,我们可以把他们放到桶数组对应的位置。

那么查找的时候,我们根据对应的名字编号,直接去找数组的下标就行了,这样一来,时间复杂度就是O(1)。

桶数组

但是老三表示,怎么会老有人的名字老叫什么三、什么四的,起码得叫个”阿刚”、”小明”吧。

那么问题来了,阿刚、小明我们应该放在哪里呢?他们没法直接放到桶数组的对应下标位置。

所以,就引入了我们第二个关键要素哈希函数

哈希函数

为了让映射能推广到所有情况,我们需要借助哈希函数 hashFunction映射到桶数组对应的位置。

例如,我们上面说到的一些平平无奇的名字,阿刚、小明……我们要把它们映射到对应的桶中。

哈希函数

一般情况下,哈希函数通过对名字的HashCode进行运算,将名字映射到桶数组对应的索引。

哈希碰撞

我们最理想的情况,就是通过哈希计算,各个元素找到空闲的坑位,但是现实往往不那么尽如人意,有时候,会发现,心上的城,已经长满了别人家的青藤。

哈希冲突

阿刚和小明映射到了同一个位置,但这个位置只能容下一个人,这就叫哈希碰撞。

所以为了尽可能避免哈希碰撞呢,就需要精心设计哈希函数,我们希望哈希函数满足以下要求:

  • 必须是一致的
  • 计算简单
  • 散列地址分布均匀

哈希函数构造方法

哈希函数的构造方法有很多,如下图,有些方法见名知义,篇幅所限,就不多讲。

哈希函数构造方法

这里提一下HashMap的哈希函数:

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

整个过程大概如下:

  • 利用hashCode()方法获取int类型的hashCode
  • hashCode的高16位和hashCode的低16位做一个异或运算

HashMap 哈希函数

hashCode右移16位,正好是32bit的一半。与自己本身做异或操作(相同为0,不同为1)。就是为了混合哈希值的高位和地位,增加低位的随机性。并且混合后的值也变相保持了高位的特征。

  • 32位int型hashCode范围过大,需要与桶数组长度取模运算,得到索引值
int index = hash & (arrays.length-1);

HashMap的哈希函数是非常优秀的设计,很值得学习。

处理哈希冲突的办法

即使再好的设计,也难免发生哈希碰撞。

那么,发生哈希碰撞,应该怎么处理呢?

拉链法

阿刚和小明在桶中发生了冲突,那我们在桶数组接一个小尾巴——用一个链表将他们俩存起来就可以了。

拉链法

除了这个,还有啥办法呢?

唉,假如我们的桶数组还是有坑位,我们可以重新分配,这就是

版权声明:本文为three-fighter原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/three-fighter/p/15130703.html