哈希表原理与实现_链地址法

C++哈希表实现原理与性能优化:从链地址法到STL unordered_map 哈希表是一种基于键值对存储的高效数据结构,通过哈希函数将任意类型键映射到固定大小的数组索引,实现接近O(1)时间复杂度的查找、插入和删除操作。其核心原理在于将数据分散存储到桶数组中,利用数组随机访问特性提升性能。哈希冲突是影响性能的关键因素,常用解决方案包括链地址法和开放地址法,其中链地址法通过链表处理冲突,实现简单且对负载因子容忍度高。在工程实践中,哈希表广泛应用于缓存系统、数据库索引、编译器符号表等场景,其性能优化涉及哈希函数设计、负载因子控制和内存管理等方面。本文以C++为例,深入探讨哈希表的底层实现 阅读详情

目录


前言

上篇文章我们用开放定址法手撕了一个哈希表,其核心思想是通过线性探测解决哈希冲突。

但开放定址法有其缺点:负载因子一大,冲突率飙升,探测路径变长,效率下降。因为开放定址法本质是“鸠占鹊巢”,没有从根本上解决冲突的问题。那么有没有不占别人位置就能解决冲突的办法?

有,这就是 C++ std::unordered_map 采用的链地址法(Separate Chaining)。

本文带你对照源码,从头实现一个链地址法哈希表。


一、链地址法的核心思想

开放定址法是"这个坑被占了,我换个坑"。链地址法是"这个桶被占了?没关系,同一个桶里可以挂一堆元素"。

数组的每个位置(桶)不再直接存数据,而是存一个链表的头指针。

   [0] -> nullptr
   [1] -> (key=19) -> (key=30) -> nullptr   ← 冲突元素挂在同一个链表上
   [2] -> (key=5)  -> (key=36) -> nullptr
   [3] -> nullptr
   [4] -> (key=13) -> nullptr
  ...

这样一来,冲突的元素只是挂在同一个桶的链表后面,不影响其他桶的查找。这才是真正的"冲突无害化"。


二、节点设计:链表节点 + 指针数组

链地址法需要两种数据结构:

结构作用
HashNode<K, V>链表节点,存 key-value + next 指针
vector<Node*>指针数组,每个元素是一个桶的头指针
template<class K, class V>
struct HashNode
{
    pair<K, V> _kv;
    HashNode<K, V>* _next;

    HashNode(const pair<K, V>& kv)
        : _kv(kv)
        , _next(nullptr)
    {}
};

哈希表的主体结构:

template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{
    typedef HashNode<K, V> Node;
public:
    // ...

private:
    vector<Node*> _tables;   // 指针数组,每个桶是一个链表头
    size_t _n = 0;           // 当前存储的元素个数
};

关键区别:开放定址法的 _tables 存的是 HashData 对象本身;链地址法的 _tables 存的是链表头指针。元素用 new 分配在堆上,是链表的节点。


三、哈希函数——复用上篇的成果

哈希函数和上篇开放定址法完全一致——默认支持整型,特化支持 string(BKDR Hash),支持自定义仿函数。这里不再赘述,直接用:

// 默认:整型直接转 size_t
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;
    }
};

四、构造函数——初始桶数有讲究

HashTable()
    : _tables(__stl_next_prime(0))
    , _n(0)
{}

__stl_next_prime(0) 会返回质数表中第一个大于等于 0 的质数——53。为什么不用 0 或者 4、8 这种 2 的幂?因为质数表长能让哈希值取模后分布更均匀,这一点上篇已经详细分析过了。


五、插入——头插法,快就一个字

5.1 为什么要头插

往链表尾部插入需要先遍历到末尾——O(n)。而头插法直接把新节点插在链表最前面:

插入前: [桶] -> A -> B -> nullptr
插入后: [桶] -> NEW -> A -> B -> nullptr

两步搞定,O(1)。

5.2 插入流程

bool Insert(const pair<K, V>& kv)
{
    Hash hash;

    // 第一步:查重
    if (Find(kv.first))
        return false;

    // 第二步:检查是否需要扩容
    if (_n == _tables.size())   // 负载因子 == 1
    {
        // ... 扩容逻辑(下一节详解)
    }

    // 第三步:头插
    size_t hashi = hash(kv.first) % _tables.size();
    Node* newnode = new Node(kv);
    newnode->_next = _tables[hashi];  // 新节点指向当前头节点
    _tables[hashi] = newnode;         // 桶指针指向新节点
    ++_n;

    return true;
}

三步走:查重 → 扩容检查 → 头插。逻辑清晰,代码简洁。

5.3 负载因子为什么是 1

上篇开放定址法的扩容阈值是 0.7,为什么链地址法可以放到 1?

因为冲突的代价不同:

  • 开放定址法:冲突了要线性探测,越满探测路径越长,冲突会传染
  • 链地址法:冲突了只是链表变长,不影响其他桶,每个桶的查找只遍历自己的链表

所以链地址法的负载因子可以放到 1 甚至更高才扩容。本文取 1.0(元素数 == 桶数时扩容),这是一个平衡选择。


六、扩容——搬节点,不是搬数据

6.1 两种扩容方式

扩容的本质是新申请一个更大的数组,然后把旧表的所有元素搬到新表。搬法有两种:

方式一:创建新表 + 重新 Insert(低效版)

// 我们在开放定址法中用的写法——在这里不推荐!
HashTable<K, V, Hash> newht;
newht._tables.resize(__stl_next_prime(_tables.size() + 1));
for (size_t i = 0; i < _tables.size(); i++)
{
    Node* cur = _tables[i];
    while (cur)
    {
        newht.Insert(cur->_kv);   // 每次都 new 新节点!
        cur = cur->_next;
    }
}
_tables.swap(newht._tables);

这种写法的问题:每次 Insert 都要 new Node,旧表的节点本该复用却被丢弃,造成了大量无意义的 new 和后续的 delete。纯纯的资源浪费。

方式二:节点迁移(高效版,推荐)

不创建新节点,而是把旧表的节点摘下来、重新链接到新表:

if (_n == _tables.size())
{
    // 1. 创建新的桶数组
    vector<Node*> newTable(__stl_next_prime(_tables.size() + 1));

    // 2. 遍历旧表,把每个节点迁移到新表
    for (size_t i = 0; i < _tables.size(); i++)
    {
        Node* cur = _tables[i];
        while (cur)
        {
            Node* next = cur->_next;  // 先记住下一个,否则等下找不到了

            // 重新计算哈希,头插到新表
            size_t hashi = hash(cur->_kv.first) % newTable.size();
            cur->_next = newTable[hashi];
            newTable[hashi] = cur;

            cur = next;  // 处理下一个节点
        }
        _tables[i] = nullptr;  // 旧桶置空,避免野指针
    }

    // 3. 交换新旧表,O(1) 完成"搬家"
    _tables.swap(newTable);
}

整个过程图解:

旧表[1]: A -> B -> nullptr         新表[1]: nullptr
                                     新表[5]: nullptr

处理 A: hash(A) % newSize = 5
        新表[5]: A -> nullptr

处理 B: hash(B) % newSize = 1
        新表[1]: B -> nullptr

旧表[2]: C -> D -> nullptr         新表[1]: B -> nullptr
                                     新表[5]: A -> nullptr

处理 C: hash(C) % newSize = 5
        新表[5]: C -> A -> nullptr   (头插,C 在 A 前面)

处理 D: hash(D) % newSize = 1
        新表[1]: D -> B -> nullptr   (头插,D 在 B 前面)

每次只做指针重定向,不需要new和delete。

6.2 节点迁移的好处

重新 Insert节点迁移(本文实现)
new 节点每个元素一次0 次
delete 旧节点析构时全部 delete0 次
内存碎片大量申请释放无
代码复杂度简单稍复杂但可控

一句话:节点迁移让扩容从"搬家"变成了"换门牌号"——东西还是那些东西,只是按新的规则重新组织了一下。


七、查找——三步定位,链表遍历

链地址法的查找思路极其自然:先定位桶,再遍历链表。

Node* Find(const K& key)
{
    Hash hash;

    // 第一步:算出在哪个桶
    size_t hashi = hash(key) % _tables.size();

    // 第二步:遍历这个桶的链表
    Node* cur = _tables[hashi];
    while (cur)
    {
        if (cur->_kv.first == key)   // 比较 key,不是比较哈希值!
        {
            return cur;              // 找到了
        }
        cur = cur->_next;
    }

    return nullptr;                  // 遍历完都没找到
}

时间复杂度分析:

  • 最好情况:桶里只有 0~1 个元素,O(1)
  • 平均情况:负载因子控制在 1.0,每个桶平均 1 个元素,接近 O(1)
  • 最坏情况:所有 key 哈希冲突,退化成一个长链表,O(n)

注意:比较的是 cur->_kv.first == key(key 值相等),不是比较哈希值!哈希值相等不代表 key 相等(哈希冲突就是这个意思)。博主在自己实现的时候踩了这个坑。


八、删除——标准单链表删除

链地址法的删除就是经典的单链表删除指定节点:

bool Erase(const K& key)
{
    Hash hash;
    size_t hashi = hash(key) % _tables.size();
    Node* prev = nullptr;
    Node* cur = _tables[hashi];

    while (cur)
    {
        if (cur->_kv.first == key)
        {
            // 找到了,分两种情况处理
            if (prev == nullptr)
            {
                // 情况1:删除的是链表头节点
                _tables[hashi] = cur->_next;
            }
            else
            {
                // 情况2:删除的是中间或尾部节点
                prev->_next = cur->_next;
            }

            delete cur;
            --_n;
            return true;
        }

        prev = cur;
        cur = cur->_next;
    }

    return false;
}
删除链表头:                        删除中间节点:

删除前: [桶] -> A -> B -> C       删除前: [桶] -> A -> B -> C
                       ↑删除目标                      ↑删除目标

删除后: [桶] -> B -> C            删除后: [桶] -> A -> C
              prev=nullptr                   prev->_next = cur->_next

对比开放定址法:开放定址法需要一个 DELETE 状态来"占坑",防止探测链断裂。链地址法直接删节点、重新链接指针即可,不需要任何标记位。这也是链地址法的一个优势。


九、析构函数——释放所有堆内存

节点都是 new 出来的,析构时必须逐一释放,否则内存泄漏:

~HashTable()
{
    for (size_t i = 0; i < _tables.size(); i++)
    {
        Node* cur = _tables[i];
        while (cur)
        {
            Node* next = cur->_next;  // 先记住下一个
            delete cur;               // 释放当前
            cur = next;               // 前进到下一个
        }
        _tables[i] = nullptr;
    }
}

遍历每个桶的链表,逐个 delete。指针数组 vector<Node*> 本身会自动析构,不用管。
写了析构函数,就要写拷贝构造和赋值重载,此处省略。

十、测试验证

#include "HashTable.h"
#include <iostream>

int main()
{
    // 插入 10 个元素,初始 53 个桶,不超过负载因子,不触发扩容
    int a2[] = { 19, 30, 5, 36, 13, 20, 21, 12, 24, 96 };
    hash_bucket::HashTable<int, int> ht2;
    for (auto e : a2)
    {
        ht2.Insert({ e, e });
    }

    // 验证查找
    cout << ht2.Find(96) << endl;   // 非空指针
    cout << ht2.Find(30) << endl;   // 非空指针
    cout << ht2.Find(19) << endl;   // 非空指针

    // 验证删除
    ht2.Erase(96);
    ht2.Erase(19);
    ht2.Erase(30);

    cout << endl;
    cout << ht2.Find(96) << endl;   // nullptr(已删除)
    cout << ht2.Find(30) << endl;   // nullptr(已删除)
    cout << ht2.Find(19) << endl;   // nullptr(已删除)

    return 0;
}

输出预期:

0000021A8B5C1E80   ← Find(96) 有地址
0000021A8B5C1990   ← Find(30) 有地址
0000021A8B5C1890   ← Find(19) 有地址

0000000000000000   ← Find(96) nullptr,删除成功
0000000000000000   ← Find(30) nullptr,删除成功
0000000000000000   ← Find(19) nullptr,删除成功

十一、开放定址法 vs 链地址法

维度开放定址法链地址法
数据结构数组直接存数据 + 状态标记指针数组 + 链表节点
冲突解决线性探测找下一个空位冲突元素挂同一个链表
删除方式懒惰删除(DELETE 占坑)直接 delete 节点
内存布局连续,缓存友好节点分散在堆上
负载因子上限通常 0.5~0.7可以到 1.0 甚至更高
额外内存开销只多一个 State 枚举每个节点多一个 next 指针
扩容代价搬运数据,重新哈希节点迁移,只改指针
最坏情况一次长探测一个长链表
实际应用较少见,如一些嵌入式场景std::unordered_map 的选择

C++ 标准库的 unordered_map 和 unordered_set 用的就是链地址法。学会本文的实现,再看标准库源码就不会觉得陌生了。


十二、总结

本文从零实现了一个链地址法哈希表,核心要点:

  1. 节点结构:HashNode 存 pair + next 指针
  2. 桶数组:vector<Node*>,每个元素是一个链表的头指针
  3. 头插法:插入 O(1),简单高效
  4. 节点迁移扩容:不 new 不 delete,只改指针指向,效率远高于重新插入
  5. 删除:标准单链表删除,prev 和 cur 配合操作
  6. 析构:遍历每个桶的链表逐一 delete,防止内存泄漏
  7. 负载因子 1.0:链地址法比开放定址法能容忍更高的负载

如有纰漏,敬请指正!

最强解析!CWRU轴承数据集Python实战:从数据加载到模型部署全流程 本文提供了一份完整的CWRU轴承数据集Python实战指南,涵盖从数据加载、预处理、特征工程到模型构建与训练的全流程。详细解析了如何利用1D-CNN、Transformer等深度学习模型进行轴承故障诊断,并介绍了模型评估、可解释性分析以及通过FastAPI部署模型的实用方法,为工业设备智能运维提供了一套可复用的解决方案。 阅读详情

相关推荐

CanOpen PDO映射实战:从原理到配置的完整指南

本文详细解析了CanOpen PDO映射的原理与配置流程,从基础通信机制到实战配置步骤,涵盖禁用PDO、清空映射、逐项映射和使能PDO等关键环节。特别针对西门子G120变频器、台达伺服和倍福CX控制器等不同厂商设备,提供了具体的配置技巧和常见问题排查指南,帮助工程师快速掌握多厂商设备间的PDO通信配置。

weixin_30718391的博客 515

哈希表(Hash Table、散列表)实现原理(哈希映射HashMap、哈希函数(取余法、乘法法、加法法)、索引bucket桶、哈希冲突、链地址法、开放地址法(线性探测法)、负载因子、哈希表扩容)

线性探测法会按照桶的顺序从当前桶向后查找空位,直到找到一个空的桶为止。每当发生冲突时,哈希表会检查下一个桶,并依此类推直到找到一个空桶。实现简单:固定大小的数组便于通过哈希函数直接定位存储位置。内存管理简化:避免了动态调整数组大小的复杂性。哈希函数稳定性:哈希函数设计上要求固定大小的数组,以保证映射过程的高效性。负载因子和扩容机制:通过扩容和调整负载因子,保持哈希表在不同规模数据下的高效性能。尽管哈希表的大小在初始时是固定的,但它通过扩容机制能够动态调整,以适应更多的元素,确保哈希表操作的高效性。

Dontla的博客 1515

SAP物料主数据表结构全解析:ABAP开发与MM模块必知必会

本文深入解析SAP物料主数据的核心表结构,涵盖MARA、MAKT、MARC、MARD及MVKE等关键表。详细阐述了物料主数据在ABAP开发和MM模块中的存储逻辑与关联关系,帮助开发者和顾问高效处理数据查询、增强功能及业务故障排查,是掌握SAP物料管理基础的必读指南。

weixin_29216957的博客 379

C++哈希表实现:从原理到链地址法实战与性能优化

哈希表是一种基于键值对存储的数据结构,其核心原理是通过哈希函数将任意大小的键映射到固定范围的数组索引,从而实现接近O(1)时间复杂度的查找、插入和删除操作。这种“用空间换时间”的设计思想,使其在需要快速数据检索的场景中具有极高的技术价值,广泛应用于数据库索引、缓存系统(如Redis)、编译器符号表及各类编程语言的标准库中。理解哈希表的关键在于掌握哈希函数的设计、哈希冲突的解决策略(如链地址法或开放寻址法)以及动态扩容机制。本文以C++为例,深入探讨链地址法的实现细节,这是std::unordered_map

weixin_28323057的博客 274

Unity跨平台存储避坑指南:persistentDataPath权限真相

Application.persistentDataPath是Unity中用于持久化数据的核心路径,表面提供跨平台抽象,实则底层访问机制因操作系统而异。其原理并非简单路径映射,而是受Android Scoped Storage、iOS Data Protection、Windows组策略及macOS隐私权限等多层安全模型动态约束。技术价值在于保障用户数据长期可靠存储,但若忽略平台差异,极易引发‘本地正常、真机崩溃’‘iOS可存、Android报错’等线上事故。典型应用场景包括游戏存档、离线缓存、自定义资源包

weixin_32073537的博客 350

kivy--入门(一)(Windows)python

从零开始学习kivy 由于课程需要写一个app,还要写报告,这就很愁人,头秃,脑壳大。 虽然学过Android,但是小编对于Java就像对牛弹琴似的,不能说完全不懂,只能说Java这个语言不太适合我,不学还不行,还是先利用python的kivy编写一个吧。 在各大网站搜索开发app,发现适合写手机app的除了Android还有kivy,所以还是先学习如何使用吧! 文章目录从零开始学习kivy前言一、安装二、添加按钮+标签三、文本输入(TextInput)总结 前言 首先推荐这篇博客kivy的安装和使用

刘念卿的博客 9964

哈希表冲突解决:链地址法与开放地址法的原理对比与代码实现

哈希函数将键映射到索引位置,理想情况下每个键对应唯一索引。但实际中,哈希函数可能产生碰撞,即多个键映射到同一索引。例如,哈希函数$h(key) = key \mod m$,当$key_1$和$key_2$满足$key_1 \equiv key_2 \pmod{m}$时,冲突发生。冲突会导致性能下降,因此需要有效解决方案。

asdfsdgss的博客 1065

PCB模块化设计24——DCDC电源模块PCB布局布线设计规范

PCB模块化设计24——DCDC电源模块PCB布局布线设计规范

qq_31444421的博客 2万+

哈希表原理与实现全解析

3.哈希存储:将要存储的数据的关键字和存储位置之间,建立起对应的关系,这个关系称之为哈希函数。极端情况效率退化:若哈希函数不佳,某一链表过长(如所有元素哈希到同一桶),查找效率会从 O(1) 退化到 O(n)(可通过链表转红黑树等优化,如 Java HashMap)。1.核心原理:将哈希值相同的冲突元素,通过链表(或其他动态结构,如红黑树)存储在同一个“桶”(哈希表数组的索引位置)中。1.哈希函数:将输入的“键”(Key)转换为一个整数(哈希值),这个整数对应哈希表中的存储位置(索引)。

2301_80981525的博客 908

OnePlus救砖指南:线刷包与OTA全量包实战解析

本文详细解析了OnePlus手机救砖的两种核心方法:线刷包与OTA全量包。针对手机变砖的不同状况(如黑屏、能进Recovery等),文章提供了清晰的判断标准和对应的实战操作指南。重点阐述了线刷包作为终极救砖手段的底层原理与详细步骤,并对比了OTA全量包作为温和修复方案的使用场景与卡刷流程,旨在帮助用户安全、有效地恢复手机系统。

weixin_28312391的博客 1313

人形机器人_双足行走动力学:基于OpenSim平台的股骨模型与建模

摘要 本文系统解析了股骨模型的生物力学特性、建模方法及应用场景,并探讨了基于OpenSim平台的股骨与42块肌肉Hill-type建模的技术流程。股骨作为人体核心承重骨骼,其几何结构、力学性能及多尺度建模方法在医学仿真、假体设计和仿生机器人领域具有重要价值。OpenSim通过逆向动力学、肌肉力优化及多物理场耦合,为临床步态分析、手术规划及仿生系统开发提供量化工具。同时对比了Hill-type模型与其他肌肉模型的优劣,并展望AI驱动建模、智能材料集成等前沿方向。未来研究将聚焦个性化仿真、实时计算优化及生物-机

一枚农夫,闲来有事聊聊天,交个朋友,让沉闷的生活畅想下 1575

C++进阶必备:哈希表深度拆解:从原理、底层再到代码,哈希表的开放寻址法 与 链地址法,哈希桶结构实现

哈希表是一种通过哈希函数将关键字映射到存储位置的散列结构,核心在于减少冲突并实现快速查找。哈希函数包括直接定址法(适用于小范围关键字)、除留余数法(推荐使用素数避免冲突)等。哈希冲突不可避免,可通过开放定址法(线性探测易导致堆积)或链地址法(哈希桶结构,冲突值挂链表)解决。负载因子需控制在0.5-0.7以平衡空间与冲突。实现时需注意:开放定址法删除需标记状态避免查找失效;链地址法扩容时节点迁移优于深拷贝;Key需转为整型(如BKDR哈希处理字符串)。红黑树虽平均效率低于哈希表,但稳定性更优。

liriying的博客 553

完整功能-《网络安全创新实践》:设计并实现一种基于Npcap的网络嗅探器(直接能运行cpp的MFC代码+实验报告)

《网络安全创新实践》:设计并实现一种基于Npcap的网络嗅探器。(直接能跑的MFC)监听所有数据包,以及监听指定IP地址的数据包;(2)能够显示完整的IP数据包首部信息,例如:版本、首部长度、数据包长度、数据包类型(协议字段)、TTL、源IP地址、目的IP地址等;(3)能够显示完整的TCP报文段信息,例如:源端口、目的端口、序号、确认号、数据部分等;(4)能够自动检测SQL注入攻击等Web攻击行为;(5)能够将监听到的数据包信息保存到文件中,以及从文件中读取并显示数据包信息;(6)具有图形化操作界面。

weixin_46531010的博客 660

C++哈希表实现:从原理到实践,深入理解链地址法与动态扩容

哈希表是一种基于键值对存储的高效数据结构,其核心原理是通过哈希函数将键映射到固定范围的索引,实现平均O(1)时间复杂度的查找、插入和删除操作。哈希冲突是哈希表设计中的关键挑战,常见的解决方案包括链地址法和开放寻址法。链地址法通过将冲突元素链接在同一个桶的链表上,有效解决了冲突问题,是工程实践中广泛采用的策略。哈希表的性能高度依赖于负载因子,当元素数量超过阈值时,需要通过动态扩容(Rehashing)重新分配所有元素,以维持高效的操作性能。在C++开发中,深入理解哈希表的内部机制,如内存管理、冲突解决和扩容策

weixin_30644369的博客 363

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

C语言 哈希表的简单实现

C语言数据结构学习

Serendipity_Shy 1724

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

散列表(哈希表)及其存储结构和特点详解

顺序存储的结构类型需要一个一个地按顺序访问元素,当这个总量很大且我们所要访问的元素比较靠后时,性能就会很低。散列表是一种空间换时间的存储结构,是在算法中提升效率的一种比较常用的方式,但是所需空间太大也会让人头疼,所以通常需要在二者之间权衡。我们会在之后的具体算法章节中得到更多的领悟。 什么是散列表 让我们想一下,若在手机通信录中查找一个人,那我们应该不会从第 1 个人一直找下去,因为这样实在是...

空杯 8443

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

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

数据结构与算法18——散列表(哈希表)

散列表(哈希表)查找 我们要在a[ ]中查找key关键字的记录: —— 顺序表查找:挨个儿比较 —— 有序表查找:二分法查找 —— 散列表查找:? 散列表的查找步骤 当存储记录时,通过散列函数计算出记录的散列地址 当查找记录时,我们通过同样的是散列函数计算记录的散列地址,并按此散列地址访问该记录。 构造散列函数的两个基本原则 好的散列函数 = 计算简单+分布均匀 直接定址法 —— 例...

Shallwen_Deng的博客 1063

数据结构之哈希表(散列表)

本文附带部分leetcode的例题和伪代码 哈希表 哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构 也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度 采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash table) 哈希表hashtable(key,value) 就是把Key通过一个固定的算法函数(哈希函数)转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,

qq_44095715的博客 1141

从零实现C++哈希表:链地址法原理、代码实现与性能优化

哈希表是一种基于键值对存储的高效数据结构,其核心原理是通过哈希函数将任意键映射到固定大小的数组索引,实现平均O(1)时间复杂度的查找、插入和删除操作。哈希冲突的解决策略直接影响性能,常见方法包括链地址法和开放定址法。在工程实践中,理解哈希表内部机制对于优化自定义类型存储、处理海量数据和高并发场景至关重要。本文以C++链地址法实现为例,深入探讨哈希函数设计、负载因子动态调整、迭代器实现等关键技术细节,并分析如何通过优化哈希函数和扩容策略来提升性能,为开发高性能存储系统提供实践参考。

weixin_30482383的博客 389

散列表分析(Java实现)

散列表分析(Java实现)一、 散列表的原理 散列表是一种空间换时间的存储结构,是在算法中提升效率的一种比较常用的方式。 散列表(哈希表),是指可以通过关键字key直接访问到内容value的一种数据结构。可以一个value对应多个key,但是一个key只能对应一个value,其中是通过key映射到一个位置上,来直接访问value。而映射位置的机制,会导致可能不同key指向相同位置,这种现象成为“

厚积薄发,持之以恒 2443

哈希表原理:解决哈希冲突的开放地址法与链地址法对比实现

若追求高空间利用率且数据量可控,开放地址法是更优选择,但其性能对负载因子敏感,需做好表大小规划。若需要灵活应对动态数据(频繁插入删除)且可接受少量额外空间开销,链地址法更易实现且性能稳定,是工业界的主流选择(如 Python 的 dict、Java 的 HashMap 均基于链地址法)。理解两种方法的核心差异,不仅能帮助开发者在实际项目中选择合适的哈希表实现,更能深入掌握哈希表 “以空间换时间” 或 “以时间换空间” 的设计思想 —— 这也是数据结构优化的核心逻辑之一。

2501_93897734的博客 969

【数据结构】 哈希表

哈希表 什么是哈希表? 哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。 哈希表hashtable(key,value) 就是把Key通过一个固定的算法函数既所谓的哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里。(或者:把任意长度的输入(又

Redisread的博客 1282

哈希表实现原理、扩容机制c++

哈希表概念和实现,C/C++实现 总结: 哈希表通过散列表实现,在存储位置和关键字之间建立一个确定的关系f,使得每个关键字key对饮一个存储位置f(key),查找时,通过这个确定的关系f,根据给定的key找到映射的位置f(key) 常用的散列函数: 除留余数法 哈希冲突 两个不同的key有相同的f(key) 解决方法: 1、开放定址法 2、链地址法(扩容:默认数组大小为16,存放量超过数组0.75时,数组大小翻倍) HashMap实现原理和扩容机制 java中哈希表由数组+链表组成 ...

weixin_43800124的博客 2456

哈希表的C实现(一)

哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。具体的介绍网上有很详细的描述,如闲聊哈希表 ,这里就不再累述了; 哈希表在像Java、C#等语言中是与生俱来的。可是在C的世界中,似乎只有自己动手,丰衣足食;在网上google了一把,大致有几个版本,我会一一...

200

例7.2连地址法(除留余数法和链地址法构造散列表)

除留余数法和链地址法构造散列表 实现 #pragma once #include <iostream> using namespace std; #define LEN 13 typedef struct hash_node { int data; struct hash_node* next; }hash_node; //表里存的是个地址,这是个HashNode*类型的数组 typedef hash_node* HashTable[LEN]; //初始化 void InitH

weixin_48192326的博客 933

哈希表查找

前面查找方法的共同特点:通过关键字值与给定值进行比较,来确定位置。效率取决于比较次数。理想的方法是:不需要比较,根据给定值能直接定位记录的存储位置。这样需要在记录的存储位置与该记录的关键字之间建立一种确定的对应关系,使每个记录的关键字与一个存储位置相对应。 哈希表的特点和结构 hashtable也叫做散列表,有多种结构,其中最常见的是采用顺序表+链表,主结构采用顺序表,每个顺序表的节点单独引出一个链表。 哈希表是如何添加数据的 计算哈希码(调用hashCode()函数,结果是一个int型的值,整数的哈希码

Nicholas_GUB的博客 1363
上一篇: C++ STL unordered_map 与 unordered_set 使用详解
下一篇: 封装哈希表模拟实现 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、付费专栏及课程。

余额充值