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)常用此实现。

总结

  • 闭散列通过探测序列解决冲突,适合内存紧凑的场景。
  • 开散列通过链表解决冲突,扩展性强,易于实现。
  • 实际选择需权衡内存效率性能需求实现复杂度
相关推荐
暮冬-  Gentle°4 小时前
C++中的命令模式实战
开发语言·c++·算法
卷福同学6 小时前
【养虾日记】Openclaw操作浏览器自动化发文
人工智能·后端·算法
春日见7 小时前
如何入门端到端自动驾驶?
linux·人工智能·算法·机器学习·自动驾驶
图图的点云库7 小时前
高斯滤波实现算法
c++·算法·最小二乘法
一叶落4388 小时前
题目:15. 三数之和
c语言·数据结构·算法·leetcode
老鱼说AI9 小时前
CUDA架构与高性能程序设计:异构数据并行计算
开发语言·c++·人工智能·算法·架构·cuda
罗湖老棍子10 小时前
【例 1】数列操作(信息学奥赛一本通- P1535)
数据结构·算法·树状数组·单点修改 区间查询
big_rabbit050210 小时前
[算法][力扣222]完全二叉树的节点个数
数据结构·算法·leetcode
张李浩10 小时前
Leetcode 15三题之和
算法·leetcode·职场和发展
2301_7938046911 小时前
C++中的适配器模式变体
开发语言·c++·算法