拉链法(链地址法)

定义

拉链法(Separate Chaining,也称链地址法)是解决哈希冲突的一种方法。其核心思想是:

哈希表的每个数组位置不直接存储键值对,而是存储一个链表(或其他容器)。所有哈希到同一索引的键值对都放入该位置的链表中。

基本原理

索引:  0       1       2       3       4
      ┌─┐     ┌─┐     ┌─┐     ┌─┐     ┌─┐
table │ │     │ ●───►│ │     │ │     │ │
      └─┘     └─┘     └─┘     └─┘     └─┘
              ● (key=1, value=A)
              ● (key=11, value=B)
              null

hash(1) = 1hash(11) = 1 时(发生冲突),两个键值对都会存储在 table[1] 的链表中。

操作复杂度

操作 平均情况 最坏情况 说明
查找 O(1) O(n) 最坏情况为所有 key 都冲突,链表退化为线性表
插入 O(1) O(n) 假设哈希函数均匀分布
删除 O(1) O(n) 需要先查找再删除

与开放寻址法对比

特性 拉链法 开放寻址法(线性探查)
存储方式 数组 + 链表 纯数组
负载因子 可以 > 1 必须 ≤ 1
缓存友好性 较差(指针跳转) 较好(连续内存)
删除操作 简单(链表删除) 复杂(需要标记删除位)
空间开销 指针开销较大 无额外指针开销

关键设计考虑

1. 底层容器选择

  • 链表:实现简单,但缓存不友好
  • 平衡树(Java 8+ HashMap):当链表长度 > 8 时转为红黑树,避免 O(n) 退化
  • 动态数组:某些场景可用,但插入删除效率低

2. 负载因子影响

  • 负载因子 = 元素总数 / 数组容量
  • 拉链法允许负载因子 > 1(一个桶可以有多个元素)
  • 但负载因子过大会导致链表过长,影响性能

3. 哈希函数要求

  • 必须均匀分布,避免大量冲突导致链表过长
  • 计算必须 O(1),否则影响整体性能

代码示例(简化版)

来自 哈希表链地址法 的简化假设:

// 哈希函数:简单取模
int hash(int key) {
    return key % table.length;
}

// put: 插入到链表头部
void put(int key, int value) {
    int index = hash(key);
    Node head = table[index];
    // 遍历链表,如果 key 存在则更新,否则插入新节点
}

// get: 遍历链表查找
int get(int key) {
    int index = hash(key);
    Node node = table[index];
    while (node != null) {
        if (node.key == key) return node.value;
        node = node.next;
    }
    return -1; // key 不存在
}

实际应用

  • Java HashMap/Hashtable:JDK 8+ 使用数组 + 链表/红黑树
  • C++ std::unordered_map:通常实现为链地址法
  • Python dict:类似链地址法,但使用开放寻址的变体

相关概念


Interactive Graph