哈希表

  今天是7.20,是计划的截止日期。前几天一直在玩滑板拖着没写博客没学习,今天下午又学习了一遍哈希表的知识,按计划该写博文完成哈希表的学习。

  哈希表(hash table)又称为散列表,是一种对实体进行索引的数据结构。它的特点是查询速度快,一个拥有好的哈希函数的哈希表,进行查找时可以实现均摊常量时间O(1)的时间复杂度,这无疑是很快速的查找速度。比方说,数组本身就是最简单的一种哈希表,根据数组下标可以快速找到下标对应的数组元素。

  对于哈希表,根据解决冲突的形式不同,有开散列和闭散列两种形式。这里的“开、闭”可以形象地认为是,主表状态是发散(开散列)的还是一维线性结构(闭散列)。

  有了对哈希表的大概了解,下面再详细介绍一下哈希表的基本组成部分。

  首先,哈希表由键-值组成(key-value)。键是实体被索引时的标识符,值是被索引的实体。举下面一个例子对键和值进行说明:

  《红楼梦》 ————>  文学类,

在以上的例子中,文学类即是键,而《红楼梦》则是值。一般使用数组元素存储值,使用数组下标表示键。

  其次,由值转化为键的过程,需要使用哈希函数,哈希函数是一种将值映射到键的规则与方法。哈希函数可以理解为值与键的对应关系。输入一个值,根据哈希函数,可以求得一个对应的键。一个好的哈希函数,会让将不同的值尽可能映射成不同的键。举下面一个例子对哈希函数进行说明:

  已知值:apple、bed、egg;

  已知哈希函数:根据实体首字母,将a对应1,将b对应2,以此类推……;

  那么组成的哈希表如下:

键(数组下标) 0 1 2 3 4 5
值(实体) NULL apple bed NULL NULL egg

  当然上面这个哈希函数并不好,因为很明显以a开头的单词多了去了,此时如果仍旧使用这个哈希函数,将导致大量冲突的产生,关于冲突的解决我们在第三部分讲解。常用的哈希函数一般会这么操作:首先将值映射到一个整数,再通过对一个大质数取模来映射到一定范围内的整数。举下面一个例子进行说明:

  cba ————> 2 * 260 + 1 * 261 + 0 * 262 = 28,

28 % 1000007 = 28,

经过这两步,就完成了一个将值映射到键的操作。

  第三,在第二部分我们简单介绍了冲突的情况,也就是不同的值恰好被映射到同一个键时怎么办。在这个部分,我们来探究如何解决冲突。解决冲突一般而言有两种办法:开散列、闭散列,这两个概念在第一部分提到过。

  开散列,是使用链表来存储每一个键对应的所有的值,值得注意的是,此时主表用来存储链表头而不存储具体的值。这是一种类似邻接表的结构,也可以使用数组模拟链表。

  闭散列,是对冲突的键按规则存储值存储的下标,主表可以直接存储具体的值,并且每个下标只能存储一个。还需要一个探测函数,当不同的值发生冲突时,使用探测函数查找下一个值,一个好的探测函数可以尽快解决冲突。最简单的探测函数如下:

  ① h(p) = p + 1 (p < k)

    ② h(p) = 0 (p = k)。

  闭散列相对于开散列,简单直接,不需要实现链表。但是也有其自身的缺点,比如说会增加整体冲突的概率,并且需要用标记删除来代替真正的删除。

  最后,谈一下哈希表的常见运用,如下:

  1. 模拟映射关系,比如说DNS解析,就是将网址映射到IP地址。
  2. 防止重复。
  3. 缓存/记住数据。

  

  

posted @ 2018-07-20 20:37  potatoKKK  阅读(141)  评论(0编辑  收藏  举报