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

总结

  • 闭散列通过探测序列解决冲突,适合内存紧凑的场景。
  • 开散列通过链表解决冲突,扩展性强,易于实现。
  • 实际选择需权衡内存效率性能需求实现复杂度
相关推荐
郭涤生1 天前
STL vector 扩容机制与自定义内存分配器设计分析
c++·算法
༾冬瓜大侠༿1 天前
vector
c语言·开发语言·数据结构·c++·算法
Ricky111zzz1 天前
leetcode学python记录1
python·算法·leetcode·职场和发展
汀、人工智能1 天前
[特殊字符] 第58课:两个正序数组的中位数
数据结构·算法·数据库架构··数据流·两个正序数组的中位数
liu****1 天前
第16届省赛蓝桥杯大赛C/C++大学B组(京津冀)
开发语言·数据结构·c++·算法·蓝桥杯
汀、人工智能1 天前
[特殊字符] 第79课:分割等和子集
数据结构·算法·数据库架构·位运算·哈希表·分割等和子集
汀、人工智能1 天前
[特殊字符] 第74课:完全平方数
数据结构·算法·数据库架构·图论·bfs·完全平方数
CoderCodingNo1 天前
【GESP】C++四、五级练习题 luogu-P1177 【模板】排序
数据结构·c++·算法
Proxy_ZZ01 天前
从零实现LDPC比特翻转译码器:C语言实战与底层逻辑解析
c语言·算法
汀、人工智能1 天前
[特殊字符] 第76课:单词拆分
数据结构·算法·均值算法·前缀树·trie·单词拆分