哈希表原理与开放定址法C++实现

数据结构:散列表(哈希表)开放定址法原理及C++实现 前言:        最近写了下RabinKarp算法,当中使用到了一定的Hash方法,我顺就复习了一下Hash表的实现原理,要不然感觉自己都要忘记了~~原理:散列表:        散列表也称哈希表,是一种键值对应的数据结构(key-value),也就是说任何一个存储在Hash表中的数据(value)都会有一个对应的键值(key)用来存储它。我们可以这样来理解他:在我们去电影院看电影的时候,我们... 阅读详情

目录


前言

假如你是一个图书管理员,有10万本书要管理。读者每次还书,你必须快速判断这本书该放回哪个书架。如果一本书一本书地去翻找,效率显然太低了。

有没有一种方法,看一眼书名,就能直接算出它该放的位置?

这就是哈希表(Hash Table) 的核心思想——建立 key 与存储位置的直接映射。

本文带你从零开始,用开放定址法手撕一个哈希表。链地址法留到下一篇再聊。


一、什么是哈希表

哈希表是一种 key-value 存储结构,通过哈希函数把 key 转换成数组下标,从而实现 O(1) 的平均查找效率。

      key ──> 哈希函数 ──> 下标 ──> 直接访问数组元素

听起来很美好,但现实很骨感——两个不同的 key 完全可能算出同一个下标,这就是哈希冲突。

解决哈希冲突有两大流派:

  • 开放定址法(Open Addressing):冲突了就在数组里找下一个空位
  • 链地址法(Separate Chaining):数组每个位置挂一个链表,冲突了就往后接

本文聚焦开放定址法。


二、三大核心问题

实现一个可用的哈希表,需要解决三个问题:

问题说明
哈希函数如何把 key 变成整数下标
冲突解决key 算出的位置被占了怎么办
扩容策略装太满了怎么扩容,扩多少

下面逐一攻破。


三、哈希函数——从 key 到下标的桥梁

3.1 基本形式

size_t hash = hashFunc(key) % _tables.size();

分两步:先用 hashFunc 把 key 映射成整数,再取模映射到数组下标范围。

3.2 整型 key 的哈希函数

整型 key 最简单——直接当 size_t 用就行:

template<class K>
struct HashFunc
{
    size_t operator()(const K& key)
    {
        return (size_t)key;
    }
};

对于 int、char、long 等类型,直接用隐式类型转换即可。

3.3 字符串 key 的哈希函数——BKDR Hash

字符串才是最常见的 key 类型。把字符串的每个字符加起来当作哈希值?太容易冲突了:"abc" 和 "cba" 会算出一样的值。

工程上常用BKDR Hash,核心是把字符累加并不断乘以一个质数(如 131),打乱字符顺序的影响:

template<>
struct HashFunc<string>
{
    size_t operator()(const string& s)
    {
        size_t hash = 0;
        for (auto ch : s)
        {
            hash += ch;
            hash *= 131;    // 每次乘131,打乱排列顺序
        }
        return hash;
    }
};

这是 C++ 的模板特化——当 K 是 string 时,编译器自动匹配这个版本。

3.4 自定义类型的哈希函数

如果你用自定义类型(比如 Date)做 key,需要自己写哈希仿函数:

struct DateHashFunc
{
    size_t operator()(const Date& d)
    {
        size_t hash = 0;
        hash += d._year;
        hash *= 131;
        hash += d._month;
        hash *= 131;
        hash += d._day;
        hash *= 131;
        return hash;
    }
};

使用时作为模板参数传入:

HashTable<Date, int, DateHashFunc> ht;

四、开放定址法——冲突了就往旁边挪

4.1 线性探测

开放定址法的思路很朴素:

你要放的位置被人占了?那往旁边挪一格看看。还被占?再挪一格……直到找到空位。

用公式表达:

hashi = (hash0 + i) % tablesize   (i = 1, 2, 3, ...)

这称为线性探测(Linear Probing)。hash0 是第一次算出的位置,每次冲突后探测距离 +1。

4.2 代码实现——Insert

bool Insert(const pair<K, V>& kv)
{
    // 1. 查重:key 已存在则插入失败
    if (Find(kv.first))
        return false;

    // 2. 负载因子 >= 0.7,触发扩容(后面细讲)
    if (_n * 10 / _tables.size() >= 7)
    {
        // ... 扩容逻辑
    }

    // 3. 线性探测找空位
    Hash hash;
    size_t hash0 = hash(kv.first) % _tables.size();
    size_t hashi = hash0;
    size_t i = 1;
    while (_tables[hashi]._state == EXIST)
    {
        hashi = (hash0 + i) % _tables.size();
        ++i;
    }

    // 4. 插入数据
    _tables[hashi]._kv = kv;
    _tables[hashi]._state = EXIST;
    ++_n;
    return true;
}

关键细节:

  • 先查重再插入:防止同一个 key 插入两次
  • 探测循环:只要位置状态是 EXIST 就继续找,直到遇到 EMPTY 或 DELETE
  • 取模保证下标不越界:每次 % _tables.size() 让探测路径在数组内循环

4.3 代码实现——Find

查找与插入的探测逻辑完全相同:

HashData<K, V>* Find(const K& key)
{
    Hash hash;
    size_t hash0 = hash(key) % _tables.size();
    size_t hashi = hash0;
    size_t i = 1;

    while (_tables[hashi]._state != EMPTY)
    {
        if (_tables[hashi]._state == EXIST
            && _tables[hashi]._kv.first == key)
        {
            return &_tables[hashi];
        }
        // 线性探测下一个位置
        hashi = (hash0 + i) % _tables.size();
        ++i;
    }
    return nullptr;
}

查找的终止条件是 _state == EMPTY:遇到真正的空位就停止,说明 key 不在表中。为什么不遇到 DELETE 就停?因为 DELETE 只是"曾经有人,现在走了",探测链不能断。


五、懒惰删除——为什么不能直接清空

5.1 问题场景

假如数组里有三个元素 A、B、C,它们发生冲突后依次存放在位置 3、4、5:

下标:  ...  [3]   [4]   [5]   [6]  ...
状态: ...  EXIST EXIST EXIST EMPTY ...
元素: ...   A     B     C           ...

如果我们直接删掉 B(把位置 4 设为 EMPTY),查找 C 时:算出的 hash0=3 → 发现 A ≠ C → 探测到 4 → 看到 EMPTY → 直接返回 nullptr,但其实 C 就在位置 5!

5.2 解决方案:三态标记

用三个状态区分节点的生命周期:

enum State
{
    EXIST,   // 存着有效数据
    EMPTY,   // 从未使用过
    DELETE   // 曾经有数据,现在被删了
};

template<class K, class V>
struct HashData
{
    pair<K, V> _kv;
    State _state = EMPTY;   // 默认是空
};

删除时标记为 DELETE,而非恢复为 EMPTY:

bool Erase(const K& key)
{
    HashData<K, V>* ret = Find(key);
    if (ret)
    {
        ret->_state = DELETE;   // 懒惰删除!
        --_n;
        return true;
    }
    return false;
}

DELETE 的作用就是告诉查找函数:“这里曾经有人,你继续往下找,别停。”


六、扩容——装太满了就要换个大房子

6.1 为什么需要扩容

开放定址法的性能严重依赖负载因子(load factor):

负载因子 = 元素个数 / 表长

负载因子越大,冲突概率越高,探测路径越长,性能退化越严重。一般把阈值设在 0.7,超过就扩容。

6.2 不能"原地扩容"

你可能会想:直接 resize 成原来的两倍不就行了?

不行! 因为数组长度变了,每个元素的哈希取模结果也跟着变了——原来在位置 5 的元素,扩容后可能要去位置 11。所以扩容 = 申请新数组 + 全部重新插入。

6.3 扩到多大?——质数优化

如果表长取 2 的幂(16、32、64…),取模运算 key % size 会退化为只取低位,分布可能非常不均匀。

工程上通常取一个质数作为表长(STL 里就是这么干的):

inline unsigned long __stl_next_prime(unsigned long n)
{
    static const unsigned long __stl_prime_list[28] = {
        53, 97, 193, 389, 769,
        1543, 3079, 6151, 12289, 24593,
        49157, 98317, 196613, 393241, 786433,
        1572869, 3145739, 6291469, 12582917, 25165843,
        50331653, 100663319, 201326611, 402653189, 805306457,
        1610612741, 3221225473, 4294967291
    };
    const unsigned long* first = __stl_prime_list;
    const unsigned long* last = __stl_prime_list + 28;
    const unsigned long* pos = lower_bound(first, last, n);
    return pos == last ? *(last - 1) : *pos;
}

这是 SGI STL 源码中的质数表,用 lower_bound 二分查找第一个 ≥ n 的质数,确保表长始终在一个优质值上增长。

6.4 扩容的代码实现

// 负载因子 >= 0.7 时扩容
if (_n * 10 / _tables.size() >= 7)
{
    HashTable<K, V, Hash> newht;
    newht._tables.resize(__stl_next_prime(_tables.size() + 1));

    for (auto& data : _tables)
    {
        if (data._state == EXIST)
        {
            newht.Insert(data._kv);
        }
    }
    _tables.swap(newht._tables);
}

步骤很清晰:

  1. 创建一个新的 HashTable 对象
  2. 用 __stl_next_prime 算出大于当前表长的质数
  3. 遍历旧表,把所有 EXIST 状态的元素重新插入到新表
  4. swap 交换新旧表格的底层 vector——O(1) 就完成了"搬家"

七、完整代码整合

把以上各部分拼起来,就得到一个可用的开放定址法哈希表:

#pragma once
#include <vector>
#include <string>
using namespace std;

enum State
{
    EXIST,
    EMPTY,
    DELETE
};

template<class K, class V>
struct HashData
{
    pair<K, V> _kv;
    State _state = EMPTY;
};

// 默认哈希函数——适用于整型
template<class K>
struct HashFunc
{
    size_t operator()(const K& key)
    {
        return (size_t)key;
    }
};

// 特化:string 的 BKDR Hash
template<>
struct HashFunc<string>
{
    size_t operator()(const string& s)
    {
        size_t hash = 0;
        for (auto ch : s)
        {
            hash += ch;
            hash *= 131;
        }
        return hash;
    }
};

// SGI STL 质数表
inline unsigned long __stl_next_prime(unsigned long n)
{
    static const unsigned long __stl_num_primes = 28;
    static const unsigned long __stl_prime_list[28] = {
        53, 97, 193, 389, 769,
        1543, 3079, 6151, 12289, 24593,
        49157, 98317, 196613, 393241, 786433,
        1572869, 3145739, 6291469, 12582917, 25165843,
        50331653, 100663319, 201326611, 402653189, 805306457,
        1610612741, 3221225473, 4294967291
    };
    const unsigned long* first = __stl_prime_list;
    const unsigned long* last = __stl_prime_list + 28;
    const unsigned long* pos = lower_bound(first, last, n);
    return pos == last ? *(last - 1) : *pos;
}

template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{
public:
    HashTable()
        : _tables(11)
        , _n(0)
    {}

    bool Insert(const pair<K, V>& kv)
    {
        if (Find(kv.first))
            return false;

        // 负载因子 >= 0.7,扩容
        if (_n * 10 / _tables.size() >= 7)
        {
            HashTable<K, V, Hash> newht;
            newht._tables.resize(__stl_next_prime(_tables.size() + 1));
            for (auto& data : _tables)
            {
                if (data._state == EXIST)
                {
                    newht.Insert(data._kv);
                }
            }
            _tables.swap(newht._tables);
        }

        Hash hash;
        size_t hash0 = hash(kv.first) % _tables.size();
        size_t hashi = hash0;
        size_t i = 1;
        while (_tables[hashi]._state == EXIST)
        {
            hashi = (hash0 + i) % _tables.size();
            ++i;
        }

        _tables[hashi]._kv = kv;
        _tables[hashi]._state = EXIST;
        ++_n;
        return true;
    }

    HashData<K, V>* Find(const K& key)
    {
        Hash hash;
        size_t hash0 = hash(key) % _tables.size();
        size_t hashi = hash0;
        size_t i = 1;
        while (_tables[hashi]._state != EMPTY)
        {
            if (_tables[hashi]._state == EXIST
                && _tables[hashi]._kv.first == key)
            {
                return &_tables[hashi];
            }
            hashi = (hash0 + i) % _tables.size();
            ++i;
        }
        return nullptr;
    }

    bool Erase(const K& key)
    {
        HashData<K, V>* ret = Find(key);
        if (ret)
        {
            ret->_state = DELETE;
            --_n;
            return true;
        }
        return false;
    }

private:
    vector<HashData<K, V>> _tables;
    size_t _n = 0;
};

八、测试验证

int main()
{
    // 测试1:string → string
    const char* words[] = { "abcd", "sort", "insert" };
    HashTable<string, string> ht1;
    for (auto e : words)
    {
        ht1.Insert({ e, e });
    }

    // 测试2:int → int(触发冲突和扩容)
    int nums[] = { 19, 30, 5, 36, 13, 20, 21, 12 };
    HashTable<int, int> ht2;
    for (auto e : nums)
    {
        ht2.Insert({ e, e });
    }

    // 测试3:自定义类型 key
    HashTable<Date, int, DateHashFunc> ht3;
    ht3.Insert({ { 2024, 10, 12 }, 1 });
    ht3.Insert({ { 2024, 12, 10 }, 1 });

    return 0;
}

验证要点:

  • ht1 测试了模版特化后的 BKDR 字符串哈希
  • ht2 用 _tables 初始大小 11,插入 8 个元素后负载因子 = 8/11 ≈ 0.73,超过 0.7,触发扩容
  • ht3 验证了自定义哈希仿函数的机制

九、开放定址法的优缺点

优点

  • 内存紧凑:所有数据存在一个数组里,没有额外的链表指针开销
  • 缓存友好:数据在内存中连续,CPU Cache 命中率高
  • 实现简单:相比链地址法,不需要维护额外的链表结构

缺点

  • 对负载因子敏感:装得越满,性能退化越严重
  • 删除麻烦:必须用懒惰删除,否则会打断探测链
  • 扩容代价大:扩容时所有元素都要重新哈希插入

十、总结与预告

本文用 C++ 从零实现了一个基于开放定址法的哈希表,核心知识点回顾:

  1. 哈希函数:将 key 映射为整数下标,BKDR Hash 是字符串哈希的首选
  2. 线性探测:冲突后依次向后查找空位
  3. 三态标记:EXIST / EMPTY / DELETE,DELETE 保证探测链不断开
  4. 负载因子:控制在 0.7 以下,超过则扩容
  5. 质数表扩容:使用预定义的质数表,避免取模分布不均匀

下一篇预告:链地址法——每个桶挂一个链表,彻底告别"探测链"的烦恼。同时还会对比两种方法在实际工程中的应用场景(std::unordered_map 用的就是链地址法)。


如有纰漏,欢迎指正!

C++哈希表实现:开放定址法原理与工程实践详解 哈希表是一种基于键值对存储的高效数据结构,其核心原理是通过哈希函数将键映射到固定范围的索引,实现近似O(1)时间复杂度的查找、插入和删除操作。在解决哈希冲突的多种策略中,开放定址法因其简洁性和缓存友好性被广泛应用,它通过线性探测、二次探测或双重哈希等方法在数组中寻找下一个可用位置。理解负载因子、动态扩容机制和墓碑标记等关键技术细节,对于优化哈希表性能至关重要。本文以C++工程实践为例,深入探讨了开放定址法的实现,特别是线性探测策略下的冲突处理与性能优化,为开发者构建高性能存储系统提供了实用参考。 阅读详情

相关推荐

哈希表查找 — 开放定址法

散列表(也叫哈希表),是根据关键字值而直接进行访问的数据结构。通过把关键字值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。散列函数的构造方法: (1)直接定址法 (2)除留余数法 (3)平方取中法 (4)折叠法 (5)数值分析法 本文采用除留余数法构造散列函数。 H(K) = (H(k) + i) % m; 其中i = 1, 2, …

learn_sunzhuli的专栏 4917

C++哈希表深度解析:开放定址法与哈希桶实现原理与性能对比

哈希表是一种基于键值对存储的高效数据结构,其核心原理是通过哈希函数将任意类型的键映射到固定范围的数组索引,从而实现平均O(1)时间复杂度的查找、插入和删除操作。哈希函数的设计需满足确定性、高效性和均匀性,以减少哈希冲突。当不同键映射到同一位置时,需通过冲突解决策略处理,这直接决定了哈希表的性能表现。在工程实践中,开放定址法将冲突元素存储在数组的其他位置,对CPU缓存友好但删除操作复杂;而哈希桶法(链地址法)则使用链表存储冲突元素,实现简单且能容忍更高的负载因子。理解这些底层机制,对于优化C++标准库中`st

weixin_27303545的博客 230

戴尔PowerEdge服务器R450 RAID配置步骤

戴尔PowerEdge服务器R450 RAID配置步骤

且听风呤tmj的博客 6909

【C++】21. 哈希表实现

本篇文章对哈希表进行了总结,包括哈希函数,哈希冲突,以及通过开放定址法和链地址法分别实现了哈希表。

2301_80475881的博客 1716

新四化浪潮下,中年整车诊断工程师的黄金突围路

摘要 本文探讨了汽车电子诊断工程师在当前行业变革中的独特优势与发展路径。作者作为从业者指出,整车诊断岗位在汽车"新四化"浪潮中呈现"逆生长"特性:技术壁垒高、经验复利强、市场需求持续增长。中年工程师凭借丰富的案例积累和实战经验成为行业稀缺人才,薪资水平显著高于同龄段其他技术岗位。文章分析了四条职业发展路径(技术专家、技术管理、细分深耕、横向拓展),并警示了三大职业风险(执行层局限、技术停滞、成果沉淀不足)。最后提出具体建议,强调掌握UDS高阶、DoIP以太网诊断等核心技术的重要性,为中年技术人提供了清晰的职

Soly_kun的博客 308

光斑大小控制【ZEMAX操作数】

在使用Zemax优化光学系统时,对成像的聚焦光斑大小进行限制,即弥散斑的大小; 一、RSCE(质心RMS光斑半径):二、RSCH(光线RMS光斑半径):

yh的博客 7855

【C++】数据结构之哈希表(散列表)

本篇文章讲解了哈希表的相关理论,并使用线性探测法与哈希桶实现了哈希表

L_0907的博客 511

无人机避障——卡内基梅隆大学(CMU)CERLAB 无人机自主框架复现

CMUCERLAB无人机自主框架是一个模块化的无人机自主导航系统,包含感知、测绘、规划和控制等组件。安装过程中遇到的主要问题包括:(1)cv_bridge路径未正确识别,需手动指定路径;(2)OpenCV版本冲突(4.2与4.6),导致动态导航节点崩溃。解决方案包括:完全卸载OpenCV4.2,重新编译安装OpenCV4.6,并修改相关CMakeLists文件指定版本。最终成功运行仿真环境,实现无人机在Gazebo和RViz中的自主避障导航功能。

weixin_45390670的博客 1643

C++ 手写哈希表(开放定址法 + 链地址法)+ 封装 unordered_map/unordered_set,从原理到工程级实现

哈希表实现

hi_ro_a的博客 921

uni-app蓝牙MTU设置失效探因:从20字节限制到跨设备兼容性实战

本文深入探讨了uni-app蓝牙开发中MTU设置失效的常见问题,特别是20字节限制的成因及解决方案。通过分析设备兼容性、蓝牙协议栈特性和系统版本差异,提供了智能延迟策略、设备特征检测和渐进式MTU提升等实战方案,帮助开发者优化蓝牙数据传输效率。

weixin_29289821的博客 287

STM32H7 Flash擦除后数据读取异常的缓存问题解析

本文深入分析了STM32H7系列芯片在Flash擦除后数据读取异常的缓存问题,揭示了由于数据缓存(D-Cache)和指令缓存(I-Cache)导致的数据一致性问题。通过详细的解决方案,包括手动维护缓存一致性和操作期间禁用缓存,帮助开发者有效解决这一常见但棘手的嵌入式开发难题。

weixin_29158413的博客 391

C++——哈希表

本文介绍了哈希表的基本原理与实现方法。哈希表通过哈希函数建立键值与存储位置的映射关系,相比红黑树具有更高的查找效率。重点分析了两种哈希映射方法:直接定址法(适合数据集中情况)和除留余数法(适合数据分散情况)。为解决哈希冲突,提出开放定址法(包括线性探测和二次探测)和负载因子控制策略。文章详细阐述了哈希表的模拟实现过程,包括状态管理、插入扩容机制和删除操作等。最后指出开放地址法在数据集中冲突时效率会明显下降的局限性。

2301_80342122的博客 2255

【DeepSeek开发入门】DeepSeek API 开发初体验

在人工智能蓬勃发展的时代,DeepSeek作为一款功能强大的AI大模型,为开发者提供了丰富的接口和便捷的开发体验。本文将基于DeepSeek的几个关键接口,带你快速入门,开启DeepSeek开发的探索之旅。通过本文,你已经初步了解了DeepSeek开发中几个重要接口的使用方法,包括列出模型、对话补全和查询余额接口,以及开发过程中的常见问题及解决办法。这是你踏入DeepSeek开发领域的第一步。在后续的学习中,你可以深入研究模型的参数调优,探索不同模型在各种场景下的应用,如利用模型实现更复杂的推理对话功能。

寻道AI,探索AI无限可能! 2752

【C++】哈希表原理与实现详解

如果您觉得我的文章还不错,欢迎多多互三分享交流,一起学习进步!

记录我一步步的学习历程,期待无限进步,向星举目! 2707

基于YOLO26的实时目标追踪实战:从训练到视频追踪(附完整可视化代码)

核心组合:YOLO26(高精度检测)+ ByteTrack(稳定追踪)是实时目标追踪的最优工业组合,Ultralytics内置集成无需额外开发;训练优化:追踪场景需优先提升检测框精度(增加box损失权重),保证ID稳定性;可视化核心:为每个ID分配唯一颜色、绘制运动轨迹、显示FPS,是落地场景的必备功能;性能调优:轻量化模型+降分辨率+OpenVINO导出,可在边缘设备(如研华工控机)实现实时追踪。这套代码可直接复用到智慧园区、停车场、工业产线等场景,仅需修改park.yaml中的类别和。

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 571

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

【c++】哈希表-开放定址法的实现

本文介绍了C++中unordered_map和unordered_set的基本概念,以及与有序容器的区别。重点讲解了哈希表的原理,包括直接定址法、除留余数法等哈希函数设计方法,以及常见的哈希冲突处理策略如线性探测和二次探测。文章通过实例演示了哈希表的存储和查找过程,并解释了负载因子的概念及其对性能的影响。最后简要提及了开放定址法中的二次探测实现方式。

mo_sheng_sli的博客 707

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

【C++】哈希表详解(开放定址法+哈希桶)

哈希表的开放定址法和链地址法(哈希桶)

有梦想的电信狗 1077

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

【C++:哈希表】从哈希冲突到负载因子:熟悉哈希表的核心机制

因为开放定址法解决冲突不管使用哪种方法,占用的都是哈希表中的空间,始终存在互相影响的问题——正因如此,

艾莉丝努力练剑的博客,感谢大家一直以来的对艾莉丝的支持和鼓励,uu们如果对C/C++方向感兴趣的话,可以通过艾莉丝主页的微信名片加艾莉丝的好友,和努力练剑的艾莉丝一起加油! 2678

【c++】哈希表实现

如果是2^x ,那么key %2^x本质相当于保留key的后X位,那么后x位相同的值,计算出的哈希值都是⼀样的,就冲突了。当key是string/Date等类型时,key不能取模,那么我们需要给HashTable增加⼀个仿函数,这个仿函数⽀持把key转换成⼀个可以取模的整形,如果key可以转换为整形并且不容易冲突,那么这个仿函数就⽤默认参数即可,如果这个Key不能转换为整形,我们就需要⾃⼰实现⼀个仿函数传给这个参数,实现这个仿函数的要求就是尽量key的每值都参与到计算中,让不同的key转换出的整形值不同。

2401_88230254的博客 571

【C++哈希表实现】:从原理到代码深度剖析

哈希表通过哈希函数将键映射到数组位置,实现O(1)平均时间复杂度。当发生哈希冲突时,开放定址法通过探测寻找新位置,链地址法则使用哈希桶。负载因子控制在0.7以下,超过则扩容。STL unordered容器采用链地址法,质数表扩容策略,结合高效哈希函数确保性能稳定。相比红黑树,它牺牲有序性换取更快的查找速度。

月夜的风吹雨的博客 1131

新手入门:用 C++ 实现开放定址法哈希表(附完整代码解析)

为什么需要State?直接用 “是否为空” 判断不行吗?一开始我也这么想,但删除元素的时候发现问题了:比如我删除了下标 5 的元素,如果把它设为EMPTY,下次查找下标 6 的元素时,遇到下标 5 的EMPTY就会停止,导致找不到 6 的元素(因为当初插入 6 是因为 5 冲突了,现在 5 设为 EMPTY,查找逻辑会误以为后面没有数据)。EXIST:这个位置有有效数据;EMPTY:这个位置从来没存过数据;DELETE:这个位置的数据被删除了,查找时要继续往后找。:键值对(比如存学号和姓名);

hello world/linux 708

C++进阶:(十)深度解析哈希表:原理、实现与实战

本文全面解析哈希表的核心原理与C++实现。首先介绍哈希表的本质——通过哈希函数实现关键字到存储位置的映射,并分析直接定址法、哈希冲突和负载因子等基础概念。随后详细讲解4种哈希函数设计方法(除法/乘法/全域散列法及非整数处理)及2种冲突解决机制(开放定址法和链地址法),结合具体示例说明线性探测、二次探测等实现细节。最后提供完整的C++代码实现:基于线性探测的开放定址法哈希表和基于链地址法的哈希桶,涵盖扩容策略、状态管理等关键技术点。文章强调哈希表设计需要平衡理论性能与工程实践,为开发者从"会用&qu

2301_79248256的博客 2281

深入理解哈希表:从原理到实现(C++ 版)

哈希表是 “时间效率” 与 “空间效率” 的完美平衡,其核心在于哈希函数设计与冲突解决策略哈希函数需让 Key 均匀分布,推荐 “除法散列法”+“质数容量”;冲突解决优先选择链地址法(哈希桶),兼顾稳定性与实现复杂度;负载因子是性能调节器,需根据冲突策略设置合理阈值(开放定址法 0.7,链地址法 1);自定义类型作为 Key 时,需实现 “哈希函数(转整数)” 和 “相等比较”,确保哈希表正常工作。

cpparf的博客 1102

数据结构四——哈希表

散列表(哈希表) 一、实现一个基于链表法解决冲突问题的散列表 哈希思想就是通过哈希函数f(key)找到在表中的位置。aKey和bKey不相等时,f(aKey) 可能等于 f(bKey)。这种现象称为冲突。 通常有两类方法处理冲突:开放定址法和拉链法。前者是将所有结点均存放在散列表T[0…m-1]中;后者通常是将互为同义词的结点链成一个单链表,而将此链表的头指针放在散列表T[0…m-1]中。 1、开...

weixin_41940752的博客 495

【C++篇】哈希表

如下图,我们删除30,会导致查找20失败,当我们给每个位置加⼀个状态标识{EXIST,EMPTY,DELETE},删除30就可以不用删除值,而是把状态改为DELETE,那么查找20时遇到EMPTY才停,就可以找到20。,如果key可以转换为整形并且不容易冲突,那么这个仿函数就用默认参数即可,如果这个Key不能转换为整形我们就需要自己实现⼀个仿函数传给这个参数,实 现这个仿函数的要求就是尽量key的每个值都参与到计算中,让不同的key转换出的整形值不同。负载因子越小,代表哈希冲突的概率越低,空间利用率越低。

2401_82677021的博客 2311

【C++】哈希表实现

本文主要介绍用C++实现哈希表

weixin_59251911的博客 885
上一篇: 从红黑树到 map/set ——看穿 STL 的封装艺术
下一篇: C++ STL unordered_map 与 unordered_set 使用详解
jinyishu_
博客等级 码龄1年 53粉丝 82原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值