FantasticMao 技术笔记
BlogGitHub
  • README
  • C & Unix
    • C
      • 《C 程序设计语言》笔记
      • C 语言中的陷阱
      • CMake 示例
      • GNU make
      • LLVM Clang
      • Nginx 常用模块
      • Vim 常用命令
    • Unix-like
      • 《深入理解计算机系统》笔记
      • 《UNIX 环境高级编程》笔记 - UNIX 基础知识
      • 《UNIX 环境高级编程》笔记 - 文件 IO
      • 《UNIX 环境高级编程》笔记 - 标准 IO 库
      • 《鳥哥的 Linux 私房菜》笔记 - 目录配置
      • 《鳥哥的 Linux 私房菜》笔记 - 认识与学习 bash
      • 《鳥哥的 Linux 私房菜》笔记 - 任务管理
      • OpenWrt 中的陷阱
      • iptables 工作机制
  • Go
    • 《A Tour of Go》笔记
    • Go vs C vsJava
    • Go 常用命令
    • Go 语言中的陷阱
  • Java
    • JDK
      • 《Java 并发编程实战》笔记 - 线程池的使用
      • 设计模式概览
      • 集合概览
      • HashMap 内部算法
      • ThreadLocal 工作机制
      • Java Agent
    • JVM
      • 《深入理解 Java 虚拟机》笔记 - Java 内存模型与线程
      • JVM 运行时数据区
      • 类加载机制
      • 垃圾回收算法
      • 引用类型
      • 垃圾收集算法
      • 垃圾收集器
    • Spring
      • Spring IoC 容器扩展点
      • Spring Transaction 声明式事务管理
      • Spring Web MVC DispatcherServlet 工作机制
      • Spring Security Servlet 实现原理
    • 其它
      • 《Netty - One Framework to rule them all》演讲笔记
      • Hystrix 设计与实现
  • JavaScript
    • 《写给大家看的设计书》笔记 - 设计原则
    • 《JavaScript 权威指南》笔记 - jQuery 类库
  • 数据库
    • ElasticSearch
      • ElasticSearch 概览
    • HBase
      • HBase 数据模型
    • Prometheus
      • Prometheus 概览
      • Prometheus 数据模型和指标类型
      • Prometheus 查询语法
      • Prometheus 存储原理
      • Prometheus vs InfluxDB
    • Redis
      • 《Redis 设计与实现》笔记 - 简单动态字符串
      • 《Redis 设计与实现》笔记 - 链表
      • 《Redis 设计与实现》笔记 - 字典
      • 《Redis 设计与实现》笔记 - 跳跃表
      • 《Redis 设计与实现》笔记 - 整数集合
      • 《Redis 设计与实现》笔记 - 压缩列表
      • 《Redis 设计与实现》笔记 - 对象
      • Redis 内存回收策略
      • Redis 实现分布式锁
      • Redis 持久化机制
      • Redis 数据分片方案
      • 使用缓存的常见问题
    • MySQL
      • 《高性能 MySQL》笔记 - Schema 与数据类型优化
      • 《高性能 MySQL》笔记 - 创建高性能的索引
      • 《MySQL Reference Manual》笔记 - InnoDB 和 ACID 模型
      • 《MySQL Reference Manual》笔记 - InnoDB 多版本
      • 《MySQL Reference Manual》笔记 - InnoDB 锁
      • 《MySQL Reference Manual》笔记 - InnoDB 事务模型
      • B-Tree 简述
      • 理解查询执行计划
  • 中间件
    • gRPC
      • gRPC 负载均衡
    • ZooKeeper
      • ZooKeeper 数据模型
    • 消息队列
      • 消息积压解决策略
      • RocketMQ 架构设计
      • RocketMQ 功能特性
      • RocketMQ 消息存储
  • 分布式系统
    • 《凤凰架构》笔记
    • 系统设计思路
    • 系统优化思路
    • 分布式事务协议:二阶段提交和三阶段提交
    • 分布式系统的技术栈
    • 分布式系统的弹性设计
    • 单点登录解决方案
    • 容错,高可用和灾备
  • 数据结构和算法
    • 一致性哈希
    • 布隆过滤器
    • 散列表
  • 网络协议
    • 诊断工具
    • TCP 协议
      • TCP 报文结构
      • TCP 连接管理
由 GitBook 提供支持
在本页
  • 数据结构
  • 散列函数
  • 解决冲突
  • 链接法
  • 开放寻址法
  • 参考资料
  1. 数据结构和算法

散列表

数据结构

散列表(hash table)是扩展于普通数组的一种数据结构。

对普通数组可以直接寻址,能在 O(1) 时间复杂度内完成对数组中任意元素的访问,但它需要为每个可能的关键字保留一个元素位置,也称为槽(slot),因此需要占用很大的存储空间,同时又由于关键字可能并不是整型类型的,因此普通数组的使用场景相对有限。

在散列表中,不是直接将关键字作为数组的下标,而是通过关键字来计算得出相应的数组下标。在计算关键字对应的数组下标时,可能会发生 冲突,即多个关键字被映射到了同一个数组下标,此时需要额外的算法来解决冲突问题,常用的算法有 链接法(Java 中的 HashMap、Redis 中的 Hash)和 开放寻址法(Python 中的 dict)。

散列函数

在散列表中,元素通过散列函数(hash function)根据关键字来计算得出对应的数组下标位置,散列函数会将关键字均匀地分散到散列表的各个槽中。当一个关键字 k 被散列函数 h 映射到了槽 h(k) 上时,h(k) 为关键字 k 的散列值,如下图所示。

                         +------+
                         |      |
+-------------+          +------+
| k1----------|--------->|      |h(k1)
|          k2-|------+   +------+
|    k3-------|---+--|-->|      |h(k3)=h(k4)
|       k4----|--/   |   +------+
+-------------+      +-->|      |h(k2)
                         +------+
                         |      |
                         +------+
                         |      |
                         +------+

当两个关键字被映射到同一个槽中时,即发生了冲突。理想情况下的解决方式是,试图选择一个合适的散列函数来避免所有的冲突,使得散列函数尽可能地实现随机性,但现实情况下总是还会有出现冲突的可能性。

解决冲突

链接法

链接法的实现方式是,把散列到同一个槽中的所有元素都放在一个链表或者红黑树(JDK 8 对 HashMap 的优化)中。

                         +------+
                         |      |
+-------------+          +------+
| k1----------|--------->|  k1  |
|          k2-|------+   +------+  +------+
|    k3-------|---+--|-->|  k3  |->|  k4  |
|       k4----|--/   |   +------+  +------+
+-------------+      +-->|  k2  |
                         +------+
                         |      |
                         +------+
                         |      |
                         +------+

当散列表能存放 n 个元素,并且散列表具有 m 个槽时,则定义 装载因子(load factor)为 n/m,用于评估链接法中一个链表平均能存储的元素个数。

在链接法中,对散列表的查询操作的最差时间复杂度为 O(n),即所有元素都散列到了同一个槽中。散列表的平均性能与散列函数关联密切,如果散列函数能把关键字均匀地分散到不同槽中,那么查询操作的平均时间复杂度可能降低到 O(1)。

如果在链接法中的链表节点为双向链表,那么散列表的插入操作和删除操作即使在最坏情况下的时间复杂度也为 O(1)。

开放寻址法

开放寻址法的实现方式是,将所有元素都存放在散列表的槽中,通过 TODO

参考资料

  • 《算法导论》第 11 章

最后更新于1年前