数据结构与算法学习笔记(四)——散列表


说完数组和链表之后,我本来想先介绍跳表的,但是想了想了还是先介绍散列表,因为散列表可以通过数组和链表结合的方式来优化我们的数据处理能力。

简介

散列表又称哈希表,它是以哈希值(key)通过散列函数运算,得到一个数组下标,然后在对应的数组下标位置存储值的,它的存储方式是key-value的形式。如图(选用了JDK8的HasMap图):
在这里插入图片描述

特征

散列函数

散列函数(这个在HashMap里有详细介绍):

这里我举个简单的散列函数,比如:hash值 % 数组长度,这是一个简单的散列函数,假如我定义了一个数组长度为8的数组,那么我通过这个散列函数计算出下标往数组里放那么会出现什么情况呢?

散列函数有优劣之分,一个优秀的散列函数能在大数据量的条件下,将所有值均匀的分布在数组上。

哈希冲突

上面的例子通过思考就可以发现,key通过散列函数计算的数组下标,在一定的程度上是会重复的,毕竟数组是有界的,那么通过散列函数计算的结果必然会出现重复的情况,这就是哈希冲突,比如:

这里只是举例子,假如:“gdfs”和“ddsf”通过上面的散列函数计算的数组下标都为2,那么这个时候我们应该如何处理呢?

处理哈希冲突有主要有两种方式:

开放寻址法

流程图(没找到,画的比较丑):
在这里插入图片描述
意思就是:如果往数组里存放数据的时候,发现该位置已经存在值了,则在原来位置的基础上往后移动一位,直至不发生哈希冲突。

对于开放寻址法方法有多种方式,往后移动,移动多少位或者重新从前面开始等等都可以。

链表法(树形)

如图(图片来自网络):

img
链表法也就是说,当发生哈希冲突的时候,我们将原来的数据和新的数据包装成一个节点,原来的数据next指向新加入的数据,这样就形成了一个链表。我们直到,链表的查询是O(n),如果我们的链表特别的长,这就会影响效率,所以这里也可以将他它包装成一个树结构,至于为什么不一开始就包装成一棵树,留给思考时间。说到这里大家就发现这其实是HashMap的数据结构,所以这边就简单介绍下,在我的HashMap章节会有详细的介绍。

除了这两种常用的方式,还有建立公共溢出区和再哈希法:

  • 建立公共溢出区:所有哈希冲突的数据存在另外一个区域。
  • 再哈希法:顾名思义就是在进行哈希处理,直到不会冲突

思考

通过上面的介绍,散列表的大概轮廓基本都有了印象,那么现在思考为什么要用散列表这种数据结构呢?
我们都知道,数组的查询效率很高,根据下标查询时间复杂度是O(1),它的增删操作时间复杂度O(n);而链表的查询时间复杂度是O(n),它的增删操作时间复杂度O(1)。所以,通过散列表将两者结合起来,散列表的查询最好的情况下是O(1),最坏的情况是O(n)——因为如果所有key通过散列函数计算的下标一样,这种情况很少见(这也是HashMap链表长度到8后转红黑树的原因);散列表的增删操作最好的情况下时间复杂度是O(1),最差的情况是O(n)。所以散列表能够结合数组+链表的优点,鉴于两者之间,是一个优秀的数据结构,如果对其优化的好,那么综合效果是大于数组和链表的。

©️2020 CSDN 皮肤主题: 游动-白 设计师:上身试试 返回首页