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

深入理解C++哈希表:从原理到实现 哈希表(Hash Table)是计算机科学中最重要的数据结构之一,它能够在平均O(1)时间复杂度下完成数据的插入、删除和查找操作。由于其高效的性能,哈希表被广泛应用于数据库索引、缓存系统(如Redis)、编译器符号表等领域。 C++标准库提供了std::unordered_map作为哈希表的实现,但理解其底层原理对于优化程序性能和解决实际问题至关重要。本文将详细介绍哈希表的基本原理、冲突解决方法,并给出C++中的实现方式(包括标准库的使用和手写实现)。 阅读详情

        在数据结构中,哈希表(Hash Table)是一种兼顾时间效率与空间效率的存储结构,其核心优势是通过 “关键字 - 存储位置” 的直接映射,实现平均 O(1) 时间复杂度的增删查改。无论是 C++ STL 中的 unordered_map/unordered_set,还是 Java 中的 HashMap,底层都基于哈希表实现。本文将从哈希表的核心概念入手,详细拆解哈希函数设计、冲突解决策略,并通过完整 C++ 代码实现两种经典哈希表(开放定址法、链地址法),帮助开发者彻底掌握哈希表的底层逻辑。

一、哈希表核心概念

在正式讲解实现前,我们需要先理清哈希表的几个基础概念,这是理解后续内容的关键。

1.1 哈希的本质:“关键字 - 位置” 的直接映射

哈希表的核心思想是:通过一个哈希函数(Hash Function),将关键字(Key)转换为哈希表中的存储位置(下标),从而实现 “直接访问”。例如,若 Key 是整数 19,哈希函数为 “取模 11”,则存储位置为 19 % 11 = 8,直接将 19 存入下标为 8 的位置。

这种设计彻底摆脱了数组 “按索引访问”、链表 “顺序查找”、红黑树 “二分查找” 的局限,是实现高效数据操作的核心。

1.2 哈希冲突:无法避免的 “小插曲”

理想情况下,每个 Key 应通过哈希函数映射到唯一位置,但实际中不同 Key 可能映射到同一个位置,这种现象称为哈希冲突(Hash Collision)。例如,Key=19 和 Key=30,通过 “取模 11” 计算:

        19 % 11 = 8

        30 % 11 = 8

两者均映射到下标 8,产生冲突。冲突无法完全避免,但可通过 “优化哈希函数” 减少冲突概率,通过 “冲突解决策略” 处理已发生的冲突。

1.3 负载因子:平衡冲突与空间的 “调节器”

负载因子(Load Factor)是衡量哈希表 “拥挤程度” 的指标,计算公式为:负载因子=哈希表容量(M)已存储元素个数(N)​

负载因子与哈希表性能直接相关:

        负载因子越大:哈希表越拥挤,冲突概率越高,访问效率越低,但空间利用率高;

        负载因子越小:冲突概率越低,访问效率越高,但空间浪费越严重。

实践中,不同冲突解决策略对应不同的负载因子阈值:

        开放定址法:负载因子通常控制在 0.7 以下(需保证有空闲位置);

        链地址法:负载因子可允许大于 1(冲突元素通过链表存储),STL 中默认阈值为 1。

二、哈希函数设计:如何让 Key “均匀分布”

哈希函数的核心目标是让 Key 尽可能均匀地映射到哈希表的各个位置,从而减少冲突。常用的哈希函数设计方法有以下几种,其中 “除法散列法” 是最经典且实用的方案。

2.1 直接定址法:简单但局限大

直接使用 Key 或 Key 的线性变换作为存储位置,例如:

        若 Key 是 [0,99] 的整数,直接用 Key 作为下标;

        若 Key 是小写字母,用 Key 的 ASCII 码 - 'a' 作为下标(如 'a'→0,'b'→1)。

适用场景:Key 范围集中且连续,无浪费空间。例如 LeetCode 第 387 题 “字符串中的第一个唯一字符”,就可用此方法统计字符出现次数:

int firstUniqChar(string s) {
    int count[26] = {0}; // 直接定址:字符→下标(ASCII-'a')
    for (char ch : s) count[ch - 'a']++; // 统计次数
    for (int i = 0; i < s.size(); i++) {
        if (count[s[i] - 'a'] == 1) return i;
    }
    return -1;
}

缺点:若 Key 范围分散(如 Key 是 [1, 1000000] 的随机数),会导致哈希表容量过大,严重浪费空间。

2.2 除法散列法(除留余数法):最常用的方案

将 Key 对哈希表容量 M 取模,结果作为存储位置,公式为:h(Key)=Key%M

关键优化:M 的选择直接影响冲突概率,需遵循以下原则:

        避免选择 2 的整数次幂(如 16、32):此时取模等价于 “保留 Key 的后 X 位”,若 Key 后 X 位重复(如 63→00111111,31→00011111),冲突概率极高;

        避免选择 10 的整数次幂(如 100、1000):仅保留 Key 的后 X 位(如 112 和 12312 对 100 取模均为 12),冲突严重;

        推荐选择 “不接近 2 的整数次幂的质数”(如 11、53、97):质数的约数少,能让 Key 分布更均匀。

特殊情况:Java HashMap 选择 2 的整数次幂作为 M,但通过 “位运算优化”(Key 高 16 位与低 16 位异或)让所有位参与计算,同样实现均匀分布,本质是 “灵活变通” 而非违背原则。

2.3 其他哈希函数(了解即可)

        乘法散列法:通过 Key 与常数 A(推荐黄金分割比 0.6180339887)相乘,取小数部分与 M 相乘后下取整,对 M 无特殊要求,但计算稍复杂;

        全域散列法:通过随机选择哈希函数(如 ha,b​(Key)=((a×Key+b)%P)%M,、 随机),避免恶意构造冲突数据,但需保证每次操作使用同一函数;

        平方取中法 / 折叠法:适用于 Key 位数较多的场景(如身份证号),通过平方后取中间几位、或拆分 Key 后叠加,将其转换为适合的整数。

三、哈希冲突解决策略:两种经典方案

        当冲突发生时,需通过特定策略为 Key 寻找新的存储位置。主流方案分为开放定址法和链地址法,两者各有优劣,适用场景不同。

3.1 开放定址法:“占满为止” 的连续空间

        开放定址法的核心逻辑:所有元素都存储在哈希表数组中,若 Key 映射的初始位置(hash0)已被占用,则按某种规则 “探测” 下一个空闲位置,直到找到为止。探测规则主要有三种:线性探测、二次探测、双重探测。

3.1.1 线性探测:简单但易 “堆积”

探测规则:从 hash0 开始,依次向后探测(若到数组末尾则回绕到开头),公式为:hi​(Key)=(hash0+i)%M(i=1,2,...,M−1)

示例:将 {19,30,5,36,13,20,21,12} 存入 M=11 的哈希表:

        19%11=8 → 存入下标 8;

        30%11=8(冲突)→ 探测 9(空闲),存入下标 9;

        5%11=5 → 存入下标 5;

        后续元素以此类推,最终存储结果如下:

下标012345678910
元素21121336-5--193020

问题:容易产生 “堆积(群集)”—— 若某段连续位置被占用(如 8、9),后续映射到该段的 Key 会持续争夺下一个空闲位置(如 10),导致冲突连锁反应。

3.1.2 二次探测:缓解堆积的 “跳跃式” 探测

探测规则:从 hash0 开始,按 “正负二次方” 跳跃探测,公式为:hi​(Key)=(hash0±i2)%M(i=1,2,...,2M​)

例如,hash0=8,M=11:

        第一次探测:8+1²=9;

        第二次探测:8-1²=7;

        第三次探测:8+2²=12%11=1;

        第四次探测:8-2²=4;

优势:跳跃式探测避免了线性探测的连续堆积,冲突分布更分散;局限:需保证 M 为 “4k+3 型质数”(如 7、11、19),否则可能无法探测到所有空闲位置。

3.1.3 开放定址法的关键设计:状态标识

开放定址法中,删除元素不能直接 “清空位置”—— 若删除下标 9 的 30,后续查找 20(下标 10)时,会因下标 9 为空而停止探测,导致查找失败。

解决方案:为每个位置增加状态标识,区分 “空(EMPTY)”“存在(EXIST)”“删除(DELETE)”:

        EMPTY:从未存储过元素,查找时遇到可停止;

        EXIST:当前存储元素;

        DELETE:元素已删除,查找时需继续探测。

3.1.4 开放定址法完整代码实现
#include <vector>
#include <algorithm>
using namespace std;

// 哈希函数仿函数(默认支持内置类型,特化支持string)
template <class K>
struct HashFunc {
    size_t operator()(const K& key) {
        return (size_t)key; // 内置类型直接转换为size_t
    }
};

// string特化:使用BKDR哈希算法(让每个字符参与计算,减少冲突)
template <>
struct HashFunc<string> {
    size_t operator()(const string& key) {
        size_t hash = 0;
        for (char ch : key) {
            hash = hash * 131 + ch; // 131是质数,增强分布均匀性
        }
        return hash;
    }
};

namespace open_address {
    // 状态标识
    enum State {
        EMPTY,   // 空
        EXIST,   // 存在
        DELETE   // 删除
    };

    // 哈希表节点:存储键值对和状态
    template <class K, class V>
    struct HashData {
        pair<K, V> _kv;
        State _state = EMPTY; // 默认空状态
    };

    // 开放定址法哈希表(线性探测)
    template <class K, class V, class Hash = HashFunc<K>>
    class HashTable {
    private:
        vector<HashData<K, V>> _tables; // 哈希表数组
        size_t _n = 0;                  // 已存储元素个数

        // 查找下一个质数(参考SGI STL实现)
        unsigned long __stl_next_prime(unsigned long n) {
            static const unsigned long prime_list[] = {
                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 = prime_list;
            const unsigned long* last = prime_list + sizeof(prime_list)/sizeof(prime_list[0]);
            const unsigned long* pos = lower_bound(first, last, n);
            return pos == last ? *(last - 1) : *pos;
        }

    public:
        // 构造函数:初始化哈希表为最小质数(53)
        HashTable() {
            _tables.resize(__stl_next_prime(0));
        }

        // 插入键值对(成功返回true,已存在返回false)
        bool Insert(const pair<K, V>& kv) {
            // 1. 检查是否已存在(去重)
            if (Find(kv.first) != nullptr) {
                return false;
            }

            // 2. 负载因子超过0.7,扩容(重新映射所有元素)
            if (_n * 10 / _tables.size() >= 7) {
                HashTable<K, V, Hash> new_ht;
                new_ht._tables.resize(__stl_next_prime(_tables.size() + 1));

                // 遍历旧表,将存在的元素插入新表
                for (auto& data : _tables) {
                    if (data._state == EXIST) {
                        new_ht.Insert(data._kv);
                    }
                }

                // 交换新旧表(现代C++深拷贝优化)
                _tables.swap(new_ht._tables);
            }

            // 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:返回节点指针(不存在返回nullptr)
        HashData<K, V>* Find(const K& key) {
            Hash hash;
            size_t hash0 = hash(key) % _tables.size();
            size_t hashi = hash0;
            size_t i = 1;

            // 遇到EMPTY停止,遇到EXIST检查Key,遇到DELETE继续
            while (_tables[hashi]._state != EMPTY) {
                if (_tables[hashi]._state == EXIST && _tables[hashi]._kv.first == key) {
                    return &_tables[hashi];
                }
                hashi = (hash0 + i) % _tables.size();
                ++i;

                // 防止死循环(理论上负载因子<1,必有空闲位置)
                if (i > _tables.size()) {
                    break;
                }
            }
            return nullptr;
        }

        // 删除Key:成功返回true,不存在返回false
        bool Erase(const K& key) {
            HashData<K, V>* data = Find(key);
            if (data == nullptr) {
                return false;
            }
            // 标记为DELETE,而非清空(避免影响后续查找)
            data->_state = DELETE;
            --_n;
            return true;
        }

        // 调试用:打印哈希表
        void Print() {
            for (size_t i = 0; i < _tables.size(); ++i) {
                if (_tables[i]._state == EXIST) {
                    cout << "[" << i << "]: " << _tables[i]._kv.first << " → " << _tables[i]._kv.second << endl;
                } else if (_tables[i]._state == DELETE) {
                    cout << "[" << i << "]: DELETE" << endl;
                } else {
                    cout << "[" << i << "]: EMPTY" << endl;
                }
            }
        }
    };
}

3.2 链地址法(哈希桶):“链表 + 数组” 的组合拳

链地址法是工业界最常用的冲突解决策略(如 C++ STL unordered_map、Java HashMap),其核心逻辑:哈希表数组存储链表头指针,冲突元素通过链表链接,每个链表称为一个 “哈希桶”。

3.2.1 核心设计:数组 + 链表的结构

        数组(哈希表主体):每个位置存储一个链表头指针,初始为 nullptr;

        链表(哈希桶):若多个 Key 映射到同一数组位置,将这些 Key 对应的节点链接成链表,挂在该位置下。

示例:将 {19,30,5,36,13,20,21,12,24,96} 存入 M=11 的哈希表:

        19%11=8 → 下标 8 链表添加 19;

        30%11=8(冲突)→ 下标 8 链表尾部添加 30;

        96%11=8(冲突)→ 下标 8 链表尾部添加 96;

        24%11=2 → 下标 2 链表添加 24(与已有的 13 链接);

        最终存储结果如下:

下标012345678910
链表-1213 → 2436-5--19 → 30 → 962021
3.2.2 优势与优化

        无堆积问题:冲突元素仅在同一链表内存储,不影响其他位置;

        负载因子灵活:可大于 1(链表可无限延长,但过长会影响效率);

        极端场景优化:当链表长度超过阈值(如 Java HashMap 阈值为 8),可将链表转为红黑树,将查找时间复杂度从 O(n) 降至 O(log n)。

3.2.3 链地址法完整代码实现
#include <vector>
#include <algorithm>
using namespace std;

// 复用之前的HashFunc仿函数(支持内置类型和string)
template <class K>
struct HashFunc { /* 同3.1.4节 */ };
template <>
struct HashFunc<string> { /* 同3.1.4节 */ };

namespace hash_bucket {
    // 哈希桶节点(链表节点)
    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 {
    private:
        vector<HashNode<K, V>*> _tables; // 指针数组:存储链表头
        size_t _n = 0;                  // 已存储元素个数

        // 查找下一个质数(同开放定址法)
        unsigned long __stl_next_prime(unsigned long n) { /* 同3.1.4节 */ }

    public:
        // 构造函数:初始化哈希表为最小质数(53)
        HashTable() {
            _tables.resize(__stl_next_prime(0), nullptr);
        }

        // 析构函数:释放所有节点(防止内存泄漏)
        ~HashTable() {
            for (size_t i = 0; i < _tables.size(); ++i) {
                HashNode<K, V>* cur = _tables[i];
                while (cur != nullptr) {
                    HashNode<K, V>* next = cur->_next;
                    delete cur;
                    cur = next;
                }
                _tables[i] = nullptr; // 避免野指针
            }
        }

        // 插入键值对(头插法,效率更高)
        bool Insert(const pair<K, V>& kv) {
            // 1. 检查是否已存在(去重)
            if (Find(kv.first) != nullptr) {
                return false;
            }

            // 2. 负载因子等于1,扩容(重新映射所有节点)
            if (_n == _tables.size()) {
                size_t new_size = __stl_next_prime(_tables.size() + 1);
                vector<HashNode<K, V>*> new_tables(new_size, nullptr);

                Hash hash;
                // 遍历旧表,将每个节点重新映射到新表
                for (size_t i = 0; i < _tables.size(); ++i) {
                    HashNode<K, V>* cur = _tables[i];
                    while (cur != nullptr) {
                        // 保存下一个节点
                        HashNode<K, V>* next = cur->_next;

                        // 计算新表中的位置
                        size_t new_hashi = hash(cur->_kv.first) % new_size;

                        // 头插法插入新表
                        cur->_next = new_tables[new_hashi];
                        new_tables[new_hashi] = cur;

                        cur = next;
                    }
                    _tables[i] = nullptr; // 旧表置空
                }

                // 交换新旧表
                _tables.swap(new_tables);
            }

            // 3. 插入新节点(头插法)
            Hash hash;
            size_t hashi = hash(kv.first) % _tables.size();
            HashNode<K, V>* new_node = new HashNode<K, V>(kv);

            new_node->_next = _tables[hashi];
            _tables[hashi] = new_node;
            ++_n;
            return true;
        }

        // 查找Key:返回节点指针(不存在返回nullptr)
        HashNode<K, V>* Find(const K& key) {
            Hash hash;
            size_t hashi = hash(key) % _tables.size();
            HashNode<K, V>* cur = _tables[hashi];

            // 遍历当前桶的链表
            while (cur != nullptr) {
                if (cur->_kv.first == key) {
                    return cur;
                }
                cur = cur->_next;
            }
            return nullptr;
        }

        // 删除Key:成功返回true,不存在返回false
        bool Erase(const K& key) {
            Hash hash;
            size_t hashi = hash(key) % _tables.size();
            HashNode<K, V>* prev = nullptr;
            HashNode<K, V>* cur = _tables[hashi];

            // 遍历链表找待删除节点
            while (cur != nullptr) {
                if (cur->_kv.first == key) {
                    // 头节点删除
                    if (prev == nullptr) {
                        _tables[hashi] = cur->_next;
                    } else {
                        // 中间/尾节点删除
                        prev->_next = cur->_next;
                    }
                    delete cur;
                    --_n;
                    return true;
                }
                prev = cur;
                cur = cur->_next;
            }
            return false;
        }

        // 调试用:打印哈希表
        void Print() {
            for (size_t i = 0; i < _tables.size(); ++i) {
                cout << "[" << i << "]: ";
                HashNode<K, V>* cur = _tables[i];
                while (cur != nullptr) {
                    cout << cur->_kv.first << "→" << cur->_kv.second << " ";
                    cur = cur->_next;
                }
                cout << endl;
            }
        }
    };
}

四、开放定址法 vs 链地址法:如何选择

两种冲突解决策略各有优劣,需根据实际场景选择:

对比维度开放定址法链地址法(哈希桶)
空间利用率高(无链表节点开销)低(需存储链表指针,节点开销大)
冲突影响范围大(堆积导致全局冲突)小(仅影响当前桶)
负载因子阈值低(通常 ≤0.7)高(可 ≥1,STL 默认 1)
删除操作复杂(需标记 DELETE,不可真删)简单(直接删除链表节点)
缓存友好性好(数组连续存储,命中缓存率高)差(链表节点分散,缓存命中率低)
适用场景数据量小、内存紧张、Key 简单数据量大、追求稳定性、工业级场景

工业界选择:链地址法因 “冲突影响小、删除简单、稳定性高”,成为主流方案(如 C++ STL、Java 集合框架);开放定址法仅在特殊场景(如嵌入式系统、内存极小)中使用。

五、总结

哈希表是 “时间效率” 与 “空间效率” 的完美平衡,其核心在于哈希函数设计与冲突解决策略:

  1. 哈希函数需让 Key 均匀分布,推荐 “除法散列法”+“质数容量”;
  2. 冲突解决优先选择链地址法(哈希桶),兼顾稳定性与实现复杂度;
  3. 负载因子是性能调节器,需根据冲突策略设置合理阈值(开放定址法 0.7,链地址法 1);
  4. 自定义类型作为 Key 时,需实现 “哈希函数(转整数)” 和 “相等比较”,确保哈希表正常工作。
基于 C++ 的链式哈希表实现详解 在数据结构的世界里,哈希表凭借其高效的查找、插入和删除操作,成为解决数据存储与检索问题的得力工具。本文将通过一段 C++ 代码,深入剖析哈希表的实现细节,带你揭开哈希表高效运作的神秘面纱。 阅读详情

相关推荐

从STL容器到自研哈希表:C++哈希表核心原理与实现详解

哈希表是一种基于键值对存储的数据结构,通过哈希函数将键映射到固定范围的索引,实现平均O(1)时间复杂度的快速查找、插入和删除操作。其核心原理在于利用数组的直接访问特性,以空间换取时间效率。当不同键映射到同一索引时,会产生哈希冲突,常见的解决策略包括链地址法和开放地址法。链地址法通过在哈希桶中维护链表来处理冲突,具有实现简单、高负载因子下性能退化平缓等优点。在工程实践中,负载因子是触发动态扩容的关键指标,扩容涉及重哈希操作,虽然成本较高,但对维持哈希表性能至关重要。哈希表广泛应用于数据库索引、缓存系统、编译器

weixin_30219613的博客 353

STL学习——hash_map的模拟实现

hash_map我们应该知道底层是基于hash表实现的。Hash_Table的开散列和闭散列的实现,我们在前几篇文章中也简单的介绍并且实现了。所以这篇文章基于之前的知识,对于哈希表做进一步的改造和封装。然后再模拟实现出我们C++STL中的hash_map。 1.哈希表的进一步改进与封装 K:关键码类型 V: 不同容器V的类型不同,如果是unordered_map,V代表一个键值对,如果是unord...

现从事游戏开发领域 | 团队项目be_with开发进行中 web方向感兴趣者私 ——We can do all things. 493

C++哈希表实现:从原理到实践构建高性能StringDictionary

哈希表是一种基于键值对存储数据的高效数据结构,通过哈希函数将键映射到固定范围的索引实现快速访问。其核心原理在于利用数组的随机访问特性,在理想情况下实现O(1)时间复杂度的查找、插入和删除操作。在C++工程实践中,哈希表是解决字符串集合管理、缓存系统和数据库索引等场景的关键技术。本文以字符串字典为具体应用,深入探讨链地址法解决哈希冲突、负载因子触发重哈希等实现细节,并分析如何通过优化哈希函数和桶内结构来提升性能。通过实现一个完整的StringDictionary类,开发者可以掌握哈希表在内存管理、模板编程和性

weixin_31742871的博客 306

使用STL的hash_map要点

说来惭愧,使用了很久Visual Stdio 2003了,只知道MFC升级到了7.0,ATL也升级到了7.0,对于这两个经典的类库做了一些研究,但一直没有注意C++标准库的变化。     今天尝试的使用了stdext::hash_map这个库,果然不错。下面写下一些心得。      hash_map类在头文件hash_map中,和所有其它的C++标准库一样,头文件没有扩展名。如下声

ztj111的专栏 5512

HashMap原理的深入理解

主要介绍了对HashMap原理的理解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

TDISP(TEE Device Interface Security Protocol)软件操作流程

/ 1. SPDM会话相关定义(🔶1-37、🔶1-187)// SPDM会话ID// AES-256-GCM加密密钥(TDISP要求)// 初始向量// 会话是否激活// 2. IDE流配置(🔶1-52、🔶1-625)// IDE流ID// 4个子流的AES密钥(覆盖所有子流类型)// 密钥是否已配置// 3. TDI状态与配置(🔶1-207、🔶1-210)TDI_CONFIG_UNLOCKED = 0, // 初始配置态。

inquisiter 1427

C++学习:哈希表的底层思路及其实现

哈希表的使用、模拟实现 哈希冲突的概念及其解决

2401_89119815的博客 1122

LAMMPS实操系列(四): 高熵合金FCC-CoCrCuFeNi的退火模拟

我们对FCC-CoCrCuFeNi的退火模拟思路主要是,首先将材料以均匀的速率用100 ps从300 K升温至1000 K,然后在1000 K的温度下平衡100 ps,随后以均匀的速率用100 ps从1000 K降温至300 K,随后通过共轭梯度下降法做静力学结构优化得到退火后的结构。由于FCC-CoCrCuFeNi高熵合金被认为是潜在的工程结构材料[1],接下来我们将对退火前后的结构进行压缩模拟,对比退火前后FCC-CoCrCuFeNi高熵合金的力学性能变化,敬请各位读者关注我们的更新。

Ceshigo_CD的博客 4836

C++ STL中哈希表 hash_map从头到尾详细介绍

目录(?)[+]0 为什么需要hash_map用过map吧?map提供一个很常用的功能,那就是提供key-value的存储和查找功能。例如,我要记录一个人名和相应的存储,而且随时增加,要快速查找和修改:岳不群-华山派掌门人,人称君子剑 张三丰-武当掌门人,太极拳创始人 东方不败-第一高手,葵花宝典 ...这些信息如果保存下来并不复杂,但是找起来比较麻烦。例如我要找"张三丰"的信息,最傻的方法就是取...

yousss的博客 10万+

RDMA学习笔记-IB五层网络协议和报文结构

Reference InfiniBand Architecture Specification Volume 1, Release 1.3 文章目录IBA 网络协议分层1.链路层1.1 LRH1.2 ICRC1.3 VCRC2.网络层(GRH)3.传输层3.1 BTH3.2 extended transport header4. 报文结构4.1 send request4.2 RDMA Write request4.3 RDMA Read request4.4 ACK4.5 Atomic operatio

u014114310的博客 9466

人工智能100问☞第5问:什么是AI三要素?

人工智能的核心三要素是​​数据、算法和算力​​,分别作为AI的“燃料”、“大脑”和“动力”,三者协同驱动智能系统的学习、推理与决策能力。

bjdx_001的博客 1991

穿越数据迷宫:C++哈希表的奇幻旅程

在C++的世界中,哈希表是一种高效、独特的数据结构,被广泛应用于需要快速查找、插入、删除的场景。通过哈希函数将数据映射到表中,它不仅提高了操作效率,还在解决冲突时展现了精巧的设计。哈希表在算法的应用中尤为重要,无论是缓存、字典处理还是集合操作,都离不开它的身影。本篇博客将带你一步步深入理解哈希表的实现原理及其应用,让你在编码之路上更上一层楼。unordered:一个不包含重复元素的集合,存储的是唯一的键。:与相似,但允许包含重复元素。:一个键值对的容器,每个键只能出现一次,键是唯一的。

17的博客 8109

国家大地2000坐标系投影坐标转高德地图经纬度坐标-【高斯投影坐标】

国家大地2000坐标系投影坐标转换成高德经纬度坐标显示画图。

devil_zhuzhu的博客 1万+

深入理解哈希表与C++中的unordered_map

哈希表是一种用于存储 键值对 的基本数据结构。在C++中,哈希表使用 哈希函数 来计算数组的索引,进而存取数组中对应索引位置的值。计算索引的过程称为哈希。哈希表也叫散列表,散列表是数组结构。可以根据一个key值来直接访问数据,因此查找速度快,在基本的数据结构中,数组的查询效率是最高的。

m0_74999866的博客 1907

三相电机控制入门:端电压、相电压、线电压到底怎么测?(附实测波形对比)

本文为三相电机控制初学者提供了端电压、相电压和线电压的完整测量指南。通过厘清三者的物理定义与测量方法,详细讲解了如何搭建虚拟星形点电路,并对比分析了方波、SPWM和SVPWM三种主流控制模式下关键电压波形的核心特征与差异,帮助工程师掌握正确的示波器测量与波形解读技巧。

oauth7security的博客 645

第42章:BRepBuilderAPI_MakeEdge(构建边)

是 OpenCascade 中用于构建边(Edge)的类。边是几何建模中的基本元素之一,通常连接两个顶点,并且可以有不同的几何形式,如直线、圆弧、样条曲线等。提供了多种创建边的方法,包括通过给定两个顶点、通过参数化曲线、通过给定起始和结束点的方式等。方法说明示例使用两个顶点创建直线边使用Geom_Curve创建边使用两点的坐标创建边Shape()获取创建的边。

yumeiguo的博客 182

深入理解哈希表:原理、实现与应用

哈希表是一种通过哈希函数将键映射到值的数据结构,支持高效的插入、删除和查找操作。// 未找到});哈希表是一种高效的数据结构,适用于需要快速查找和更新的场景。通过理解其原理和实现方法,我们可以更好地应用它解决实际问题。

共产主义接班人 822

Trae辅助开发完全教程:从入门到企业级实战

《Trae AI IDE完全教程》系统介绍了这款AI驱动的下一代集成开发环境。教程从基础入门到实战应用,详细讲解了Trae的双重开发模式(IDE/SOLO)、智能编程助手CUE、行内对话、模型管理等核心功能。内容包括环境配置、项目创建、Python开发示例,以及RPC框架和iOS应用两个实战案例。Trae通过深度整合AI技术,显著提升编码效率,支持多种编程语言和主流模型,适合从新手到企业级开发者的不同需求。本教程帮助开发者快速掌握Trae的使用技巧,实现AI辅助下的高效编程。

MrLi的博客 763

算法入门:深入理解哈希表(C++实现详解)

哈希表是一种高效的数据结构,可实现接近O(1)时间复杂度的快速查找。文章系统介绍了哈希表的原理、实现及应用:首先解释其核心机制是通过哈希函数将键映射到存储位置;然后详细分析两种主流冲突解决方法(链地址法和开放寻址法);接着展示C++ STL中unordered_map的使用和手动实现链地址法哈希表的完整代码;最后探讨性能优化技巧及实际应用场景(如LeetCode题目)。掌握哈希表的设计原理和实现方法,对提升算法效率具有重要意义。

Jay_515的博客 751

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

编程智慧之光:深入理解哈希表的实现原理,掌握数据结构精髓(c++实现)

目录1. 哈希的概念1.1 直接定址法1.2 哈希冲突1.3 负载因子1.4 将关键字转为整数1.5 哈希函数1.5.1 除法散列法/除留余数法1.5.2 乘法散列法1.5.3 全域散列法1.5.4 其他方法1.6 处理哈希冲突1.6.1 开放定址法线性探测⼆次探测 双重散列 1.6.2 开放定址法代码实现开放定址法的哈希表结构扩容 key不能取模的问题1.6.3 链地址法 解决冲突的思路扩容 极端场景1.6.4 链地址法代码实现 而哈希映射有许多方法可以实现,我们在这里介绍几种方法,有些需要我

qq_58761784的博客 1317

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

【C++高阶】哈希函数底层原理探索:从算法设计到实现优化

在数字世界的浩瀚宇宙中,哈希算法如同星辰般璀璨,以其独特的方式照亮了数据处理与信息安全的道路。它们不仅是现代计算体系中的基石,更是连接数据安全、高效检索与分布式系统的桥梁。然而,当我们谈论哈希时,往往更多地聚焦于其上层应用与宏观效果,而忽视了支撑这些奇迹的底层机制与实现细节

EterNity_TiMe_的计算机学习之路 2527

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

【数据结构】哈希表(线性探测法)

哈希表是一种搜索结构,当数据量大时,哈希搜索的效率高,平均时间复杂度O(1)。 【哈希查找】: (1)在插入时,根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放。 (2)在搜索时,对元素的关键码进行同样的计算,把求得的函数值当作元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功。 该方式即散列方法(Hash Method),在散列方法中使用的转换函数...

lz201788的博客 7万+

【C++】2.7 哈希表及其实现(附代码)

int _year;int _month;int _day;return has;因此,哈希表做key的要求:能转为整型,可以有不等于的比较:_kv(kv){ }为什么单链表:单链表对于哈希表已经足够了,还可节省空间为什么不用list或forward_list因为在扩容时,用std的list会自动析构,但是我们其实可以回收利用原先的节点,节省开销。

m0_68617924的博客 1020

数据结构(c++版):深入理解哈希表

哈希表是一种高效的数据结构,通过哈希函数将键映射到存储位置,实现平均O(1)时间复杂度的查找、插入和删除操作。本文以图书馆为喻,详细解析了哈希表的核心组件:哈希节点(图书)、哈希函数(图书归类算法)和冲突处理(链地址法)。代码实现展示了哈希表的构建、插入、查找和删除操作,并讨论了哈希冲突的解决方案。哈希表优势在于快速访问和灵活扩容,但也存在内存占用和冲突处理等局限。通过生动的比喻和代码示例,深入浅出地阐述了哈希表的工作原理和应用场景。

2503_90937448的博客 1228

【C++】使用哈希表封装unordered_set和unordered_map!

哈希表封装unordered_map和unordered_set 摘要 本文详细讲解了如何使用哈希表作为底层容器封装实现unordered_map和unordered_set。主要包括以下几个关键点: 框架分析:通过源码分析,说明hash_map和hash_set复用同一个hashtable实现key和key/value结构。 封装步骤: 实现哈希表(链地址法) 封装unordered_map/set框架,解决KeyOfT问题 实现迭代器(iterator和const_iterator) 保证Key不可修改

2403_86779341的博客 1309

哈希表C++实现--hash_table

1、hash_table.h #include 3 #include 4 #include 5 #include 6 #include 7 using namespace std; 8 9 struct table_node 10 { 11 int _key; 12 string _value; 13 struct tabl

zhou753099943的专栏 5826
上一篇: C++ 容器进阶:unordered_map 与 unordered_set 全面解析
下一篇: 从哈希表到容器封装:手把手实现 myunordered_map 和 myunordered_set
cpparf
博客等级 码龄2年 48粉丝 65原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值