极客时间课程《数据结构与算法之美》笔记06

散列表(Word文档中的单词拼写检查功能)

优势

  • 模拟映射关系
  • 防止重复
  • 缓存、记住数据,以免服务器再通过处理生成。
  • 查找、插入、删除都非常快。
  • 可以结合散列函数和数组来创建散列表,一般编程语言都提供了实现。

散列表执行各种操作的时间都为O(1),常量时间,无论散列表多大,所需时间都相同。
平均情况下,查找与数组一样快,插入和删除速度与链表一样快。
填装因子: = 散列表占用位置 / 位置总数
填装因子越大,说明空闲位置越少,冲突越多,性能会下降。
一个小的经验规则:一旦填装因子大于0.7,就调整散列表的长度。
一个好的散列函数:SHA。

思想

数组支持按照下标随机访问数据的特性,散列表是数组的一种扩展,由数组演化而来。

散列冲突

  • 开放寻址法
    出现了冲突就重新探测一个空闲位置插入。
    比如线性检测
    查找的过程中,遍历到空闲位置结束。
    删除的时候一般不是置空,而是标记为delete,因为置空会影响线性探测。
    二次探测:二次探测和线性探测相比,探测的步长为2。
    双重散列:不仅仅使用一个散列函数,如果第一个散列函数冲突,就使用第二个散列函数,依次类推。

  • 链表法
    每个元素位置再接一个链表。那个元素位置叫做桶或槽。多增加了链表的遍历。
    时间复杂度和链表的长度成正比。散列比较均匀的话,k = n/m,n = 数据个数,m是槽的个数。

词典检查就是把所有单词存储在散列表中进行查找,验证是否能找到。空间不大,放在内存里就行。

工业级散列表设计

动态扩容,均摊扩容(插入到新的散列表中,同时把老的散列表中一个数据拿到新的里面。)

当数据量比较小、装载因子小的时候,适合采用开放寻址法。这也是Java 中的 ThreadLocalMap 使用开放寻址法的原因。

虽然链表法比较耗费空间,但是如果存储的是大对象,那么链表中的结点占用空间很少,所以可以忽略不计。
比较适合存储大对象,大数据量的散列表,可以用红黑树代替链表。

HashMap 分析

默认是初始大小是16,可以调节。
最大装载因子是0.75,启动扩容会扩大到两倍大小。

底层使用链表法解决冲突,JDK1.8版本中,链表长度超过8时,链表转化为红黑树。当红黑树结点少于8个的时候,红黑树转化为链表。

以下为其散列函数。

int hash(Object key) {
    int h = key.hashCode();//返回Java对象的hash code
    return (h ^ (h >>> 16)) & (capitity -1); //capicity 表示散列表的大小
}

散列表和链表的结合

添加、删除、查找操作时间复杂度降到O(1)。
散列表中嵌入双向链表,双向链表增加特殊字段hnext。
结点除了可以使用前驱后继指针访问之外,还可以通过hnext索引访问(拉链)。
疑惑点:为何查找到一个结点之后将其放在链表尾部。

Redis有序集合
Redis和跳表关系很紧密

Java LinkedHashMap
LinkedHashMap可以支持按照插入顺序遍历数据,还支持按照访问顺序来遍历数据。
Linked指的是双向链表。
访问完数据之后也会放到链表结尾,估计是LRU策略(最近最少使用)

小结

散列表无法按照某种顺序快速遍历,一个方法是拷贝到数组中,排序遍历。
散列表是动态数据结构,不停有插入、删除情况。如果按照顺序遍历散列表,那么常常会将散列表和链表(或者跳表)一块使用。

原文地址:https://www.cnblogs.com/JackKing-defier/p/11163031.html