C++哈希表实现:开散列与闭散列详解

好的,我们来详细讲解C++中哈希表的两种常见实现方式:开散列 (Open Hashing / Separate Chaining)和闭散列(Closed Hashing / Open Addressing)。


哈希表基础

哈希表通过哈希函数将键映射到数组中的索引位置。理想情况下,每个键映射到唯一索引,但不同键可能映射到相同位置,称为哈希冲突。解决冲突是哈希表实现的关键。


1. 闭散列(Open Addressing)

闭散列中,所有元素都存储在数组本身中。发生冲突时,按照特定规则(探测序列)寻找下一个空槽位。

常见探测方法
  • 线性探测 :冲突后,顺序检查下一个槽位(索引+1)。
    • 公式:h_i(k) = (h(k) + i) \\mod N,其中 N 为表大小。
  • 二次探测 :避免线性探测的聚集问题,探测步长为平方。
    • 公式:h_i(k) = (h(k) + c_1 i + c_2 i\^2) \\mod N
  • 双重哈希 :使用第二个哈希函数计算步长。
    • 公式:h_i(k) = (h_1(k) + i \\cdot h_2(k)) \\mod N
特点
  • 优点
    • 内存连续,缓存友好。
    • 无需额外链表结构。
  • 缺点
    • 装载因子(\\alpha = \\frac{\\text{元素数}}{\\text{表大小}})需严格控制(通常 \\alpha \< 0.7),否则性能下降。
    • 删除操作复杂(需标记为"已删除"而非直接清空)。
示例代码(线性探测)
cpp 复制代码
class HashTable {
private:
    vector<int> table; // 存储数据
    vector<bool> deleted; // 标记删除状态
    size_t capacity;
    size_t size = 0;

    size_t hash(int key) {
        return key % capacity;
    }

public:
    HashTable(size_t cap) : capacity(cap) {
        table.resize(cap, -1); // -1表示空槽
        deleted.resize(cap, false);
    }

    bool insert(int key) {
        if (size >= capacity) return false;
        size_t idx = hash(key);
        while (table[idx] != -1 && !deleted[idx]) { // 寻找空槽或已删除槽
            if (table[idx] == key) return false; // 键已存在
            idx = (idx + 1) % capacity;
        }
        table[idx] = key;
        deleted[idx] = false;
        size++;
        return true;
    }

    bool find(int key) {
        size_t idx = hash(key);
        size_t start = idx;
        do {
            if (table[idx] == key && !deleted[idx]) return true;
            if (table[idx] == -1 && !deleted[idx]) break; // 遇到未删除的空槽
            idx = (idx + 1) % capacity;
        } while (idx != start);
        return false;
    }

    bool erase(int key) {
        size_t idx = hash(key);
        size_t start = idx;
        do {
            if (table[idx] == key && !deleted[idx]) {
                deleted[idx] = true; // 标记删除
                size--;
                return true;
            }
            if (table[idx] == -1 && !deleted[idx]) break;
            idx = (idx + 1) % capacity;
        } while (idx != start);
        return false;
    }
};

2. 开散列(Separate Chaining)

开散列中,每个数组槽位是一个链表(或其它容器)。冲突时,元素直接添加到对应链表中。

特点
  • 优点
    • 装载因子可更高(\\alpha \> 1 仍有效)。
    • 删除操作简单(直接移除链表节点)。
  • 缺点
    • 指针开销大,内存碎片化。
    • 缓存不友好(链表节点可能分散)。
示例代码
cpp 复制代码
class HashTable {
private:
    vector<list<int>> buckets; // 每个桶是一个链表
    size_t capacity;

    size_t hash(int key) {
        return key % capacity;
    }

public:
    HashTable(size_t cap) : capacity(cap) {
        buckets.resize(cap);
    }

    bool insert(int key) {
        size_t idx = hash(key);
        for (auto it = buckets[idx].begin(); it != buckets[idx].end(); ++it) {
            if (*it == key) return false; // 键已存在
        }
        buckets[idx].push_back(key);
        return true;
    }

    bool find(int key) {
        size_t idx = hash(key);
        for (int val : buckets[idx]) {
            if (val == key) return true;
        }
        return false;
    }

    bool erase(int key) {
        size_t idx = hash(key);
        for (auto it = buckets[idx].begin(); it != buckets[idx].end(); ++it) {
            if (*it == key) {
                buckets[idx].erase(it);
                return true;
            }
        }
        return false;
    }
};

对比与选择

特性 闭散列 开散列
内存布局 连续数组 数组+链表/树
装载因子限制 较低(通常 \\alpha \< 0.7 较高(可 \\alpha \> 1
删除复杂度 需特殊标记 直接删除节点
缓存友好度
实现难度 中等(需处理探测序列) 简单
适用场景
  • 闭散列:对缓存性能要求高、内存受限的场景(如嵌入式系统)。
  • 开散列 :通用场景,标准库(如 std::unordered_map)常用此实现。

总结

  • 闭散列通过探测序列解决冲突,适合内存紧凑的场景。
  • 开散列通过链表解决冲突,扩展性强,易于实现。
  • 实际选择需权衡内存效率性能需求实现复杂度
相关推荐
别动我齐刘海2 分钟前
ROS2 Jazzy + C++ 实战路线——进阶学习3
c++·人工智能·vscode·python·算法·机器学习·机器人
zhangfeng11338 分钟前
《从“人工适配“到“智能生成“:KernelSwift 跨国产芯片算子迁移全栈方案解读》 —— 强调范式跃迁和跨硬件属性,适合偏架构分析的写法
人工智能·算法·华为·ai编程·npu
0+11111 分钟前
算法 --滑动窗口
c++·算法·leetcode
reikocao1 小时前
大场景下 Hessian 特征值放大与退化检测
算法
hetao17338371 小时前
2026-09-18 hetao1733837 的刷题记录
c++·算法
wuyk5551 小时前
21.计数排序:不用比较的“空间换时间”排序算法
python·算法·排序算法
Rabitebla1 小时前
【Linux系统编程】 指令(二):一条路径是怎么定位到文件的
linux·c++·笔记·学习·算法
库玛西2 小时前
数据结构与算法之美:树、森林与二叉树全解析
c语言·数据结构·笔记·考研·算法·学习方法
木井巳2 小时前
【记忆化搜索】最长递增子序列
java·算法·leetcode·深度优先·剪枝·推荐算法
东方护航数据恢复(深圳)2 小时前
本地小算力设备数据恢复经典案例实操全解_东方护航数据恢复深圳店
大数据·服务器·算法·ai·gpu算力