哈希链表 vs 数组加强哈希表

对比两种基于哈希表的组合数据结构设计

概述

维度 哈希链表 (Hash Linked List) 数组加强哈希表 (Hash Table + Array)
别名 LinkedHashMap Hash Map with Array / Randomized HashMap
核心目标 维护键的插入顺序 O(1) 时间随机返回一个键 (randomKey)
组合方式 哈希表 + 双向链表 哈希表 + 紧凑数组
来源文章 哈希表与链表组合原理 用数组加强哈希表

数据结构设计对比

哈希链表(LinkedHashMap)

哈希表 table 数组
[桶0] → Node(key1, val1) ⇄ Node(key2, val2)  ← 桶内链表(拉链法)
[桶1] → Node(key3, val3)
[桶2] → null
...

双向链表维护所有节点的插入顺序:
head ⇄ Node(key1) ⇄ Node(key2) ⇄ Node(key3) ⇄ tail

额外维护

  • 每个节点:哈希表指针(next用于桶内冲突)+ 链表指针(prev/next用于全局顺序)
  • 全局 head 和 tail 指针

数组加强哈希表

哈希表 (key → index in array)
┌─────────┬─────────┐
│ key1    │ 0       │
│ key2    │ 1       │
│ key3    │ 2       │
└─────────┴─────────┘

紧凑数组 (无空洞)
┌─────────┬─────────┬─────────┐
│ key1    │ key2    │ key3    │
└─────────┴─────────┴─────────┘
   ↑ 0        ↑ 1       ↑ 2

额外维护

  • 哈希表记录每个 key 在数组中的索引
  • 紧凑数组存储所有 key(用于 O(1) 随机访问)

操作复杂度对比

操作 哈希链表 数组加强哈希表
get(key) O(1) 哈希表查找 O(1) 哈希表查找
put(key, val) O(1) 哈希表插入 + 链表追加 O(1) 哈希表插入 + 数组追加
remove(key) O(1) 哈希表删除 + 链表摘除 O(1) 用末尾元素填补 + 更新哈希表
遍历顺序 O(n) 按插入顺序 O(n) 无序(按数组顺序)
randomKey() O(n) 需要遍历链表 O(1) 随机数组索引

删除操作细节(数组加强哈希表)

删除 key2 (索引1):
1. 数组末尾元素 key3 移到位置1
2. 数组末尾删除(size--)
3. 更新哈希表:key3 → 1

数组变化:
删除前: [key1, key2, key3]
删除后: [key1, key3]

关键点:用末尾元素填补空缺,避免数组空洞,保持 O(1) 操作。

使用场景对比

场景 推荐结构 原因
需要保持插入顺序 哈希链表 双向链表天然维护顺序
需要按插入顺序遍历 哈希链表 keySet() 按插入顺序返回
需要 O(1) 随机返回键 数组加强哈希表 紧凑数组支持随机访问
实现 LRU 缓存 哈希链表 访问顺序 + 插入顺序结合
游戏/抽样场景 数组加强哈希表 需要均匀随机选择一个键

空间开销对比

开销类型 哈希链表 数组加强哈希表
每个节点指针 3个(next桶内 + prev/next全局) 1个(数组索引,存于哈希表)
额外全局指针 head + tail 无(数组本身有序)
数组存储 仅哈希表 table table + 紧凑数组(存储所有key)

结论:两种结构空间开销相近,选择取决于功能需求。

与标准哈希表的关系

标准哈希表 (HashMap)
    ├─→ 哈希链表 (LinkedHashMap) :增加顺序维护
    └─→ 数组加强哈希表 (RandomizedHashMap) :增加随机访问能力

相关概念


Interactive Graph