数据结构和算法-哈希表

186 阅读2分钟

定义

哈希表(Hash table,也叫散列表),是数组的拓展。数组支持通过索引以O(1)的时间复杂度随机访问到值,而哈希表支持通过任意类型的key值以O(1)的时间复杂度访问到值。

实现

哈希表实现会同时用到数组和链表两种基础的数据结构。

实现一个哈希表需要考虑的问题比较多,主要有以下三个

  1. 哈希函数的设计
  2. 哈希冲突的解决
  3. 扩容和缩容

哈希函数

将任意类型的key值通过某种规则映射到数组下标,这种映射规则就称为函数函数。

index := hash(key)

哈希函数是哈希表的基础,良好的设计可以减少哈希冲突的出现的概率,提升整体性能稳定性。

哈希函数中会使用到哈希算法,哈希函数是哈希算法的一种应用,哈希函数 ≠ 哈希算法

哈希冲突

哈希函数是将一个无限的key集合映射到有限的数组下标,所以不可避免的会出现冲突问题,即两个不同的key值经过哈希函数计算后,得到的数组下标是一样的。

一般应对哈希冲突的思路有以下两种:

  • 开放寻址

在数组中寻找下一个空闲位置,常见的算法包括但不局限于:线性探测、二次线性探测、重哈希

  • 链地址

新建一个链表(或者升级版:跳表/BST),来存储发生冲突的值

扩容和缩容

随着数组的可用空间越来越少,候插入元素发生哈希冲突的概率会明显增加,性能稳定性也会大大下降。

为了避免出现这种情况,可以设置一个阈值,当被使用的数组空间大于这个阈值时,对数组进行扩容。哈希表中把这个阈值称为装载因子

装载因子 = 被使用的数组空间 / 数组总空间

同理,但装载因子变小时,为了节省空间,也要考虑缩容。

发生扩容时,当前插入操作的性能会退化到O(n),均摊到每次插入操作,整体来看性能还是O(1)。

算法

假设哈希冲突使用链表法解决,不考虑扩容。

  • 最好时间复杂度:O(1)
  • 最坏时间复杂度:取决于链表法使用的数据结构,如果是单链表就是O(n)

插入元素

mymap := map[string]string{}
mymap["apple"]="red"

查找元素

// output: red
fmt.println(mymap["apple"])

删除元素

delete(mymap, "apple")

关于遍历

因为插入数据时随机映射到数组索引的,而遍历哈希表就是遍历数组,所以哈希表的遍历是一个固定的随机结果。

for k, v := range mymap {
    //fmt.println(k+v)
}