C++经典的数据结构与算法之哈希表详解(Hash Table)

 更新时间:2026年06月19日 08:22:16   作者:bkspiderx  
哈希表(Hash Table)是一种常用的数据结构,它通过一个哈希函数将关键字映射到表中一个位置来访问记录,以支持快速的插入、删除和查找操作,本文给大家介绍C++经典的数据结构与算法之哈希表(Hash Table),感兴趣的朋友一起看看吧

哈希表(Hash Table):高效的键值对存储结构

哈希表(又称散列表)是一种通过哈希函数将键(Key)映射到值(Value)的高效数据结构,它能在理想情况下达到O(1)的插入、删除和查找效率,是实现字典、缓存等功能的核心结构。

哈希表的基本操作

  • 插入(Insert)‌:将一个新的键值对添加到哈希表中。
  • 查找(Search)‌:根据键来查找对应的值。
  • 删除(Delete)‌:根据键来删除对应的键值对。

一、哈希表的核心原理

哈希表的工作流程基于三个关键组件:

  1. 哈希函数(Hash Function):将键映射到数组的索引位置,记为 hash(key) = index
  2. 哈希表数组:存储值的数组,索引由哈希函数计算得出。
  3. 冲突解决机制:当不同键映射到同一索引时(哈希冲突)的处理策略。

1. 哈希函数设计

理想的哈希函数应满足:

  • 确定性:同一键始终映射到同一索引。
  • 均匀性:键分布均匀,减少冲突。
  • 高效性:计算快速。

常见哈希函数示例(整数键):

// 取模哈希(最常用)
int hashFunction(int key, int tableSize) {
    return key % tableSize; // 确保索引在数组范围内
}
// 字符串哈希(将字符ASCII值组合)
int hashString(const string& key, int tableSize) {
    int hash = 0;
    for (char c : key) {
        hash = (hash * 31 + c) % tableSize; // 31是质数,减少冲突
    }
    return hash;
}

2. 哈希冲突解决

即使哈希函数设计良好,冲突仍不可避免,常见解决方法有:

开放地址法:冲突时寻找下一个空位置(线性探测、二次探测等)。

// 线性探测:冲突时索引+1,循环查找
int linearProbe(int index, int i, int tableSize) {
    return (index + i) % tableSize; // i为探测次数
}
  • 链地址法(拉链法):每个数组元素是链表(或红黑树),冲突元素直接加入链表。
    • 优点:处理简单,不易产生聚集,适合频繁插入删除。
    • 缺点:需额外空间存储指针。

二、基于链地址法的哈希表实现

链地址法是实际应用中最常用的冲突解决策略(如Java的HashMap、C++的unordered_map),下面实现一个完整的哈希表:

#include <iostream>
#include <vector>
#include <list>
#include <string>
using namespace std;
template <typename K, typename V>
class HashTable {
private:
    // 键值对结构
    struct Entry {
        K key;
        V value;
        Entry(K k, V v) : key(k), value(v) {}
    };
    vector<list<Entry>> table; // 哈希表数组(每个元素是链表)
    int size;                  // 当前元素数量
    int capacity;              // 表容量
    const double loadFactorThreshold = 0.7; // 负载因子阈值(触发扩容)
    // 哈希函数(整数键)
    int hash(const K& key) const {
        // 对整数键直接取模
        return key % capacity;
    }
    // 哈希函数(字符串键)- 模板特化
    int hash(const string& key) const {
        int hash = 0;
        for (char c : key) {
            hash = (hash * 31 + c) % capacity;
        }
        return hash;
    }
    // 扩容操作
    void resize() {
        int oldCapacity = capacity;
        capacity *= 2; // 容量翻倍(通常取质数)
        vector<list<Entry>> newTable(capacity);
        // 重新哈希所有元素到新表
        for (int i = 0; i < oldCapacity; i++) {
            for (const Entry& entry : table[i]) {
                int index = hash(entry.key);
                newTable[index].push_back(entry);
            }
        }
        table.swap(newTable); // 替换为新表
    }
public:
    // 构造函数:初始容量默认31(质数)
    HashTable(int initialCapacity = 31) : capacity(initialCapacity), size(0) {
        table.resize(capacity);
    }
    // 插入键值对(若键已存在则更新值)
    void insert(const K& key, const V& value) {
        // 检查负载因子,超过阈值则扩容
        if ((double)size / capacity >= loadFactorThreshold) {
            resize();
        }
        int index = hash(key);
        // 检查是否已存在该键,存在则更新值
        for (Entry& entry : table[index]) {
            if (entry.key == key) {
                entry.value = value;
                return;
            }
        }
        // 不存在则插入新键值对
        table[index].emplace_back(key, value);
        size++;
    }
    // 删除键值对(成功返回true)
    bool remove(const K& key) {
        int index = hash(key);
        for (auto it = table[index].begin(); it != table[index].end(); ++it) {
            if (it->key == key) {
                table[index].erase(it);
                size--;
                return true;
            }
        }
        return false; // 键不存在
    }
    // 查找键对应的值(找到返回true,值通过引用传出)
    bool find(const K& key, V& value) const {
        int index = hash(key);
        for (const Entry& entry : table[index]) {
            if (entry.key == key) {
                value = entry.value;
                return true;
            }
        }
        return false; // 键不存在
    }
    // 获取当前元素数量
    int getSize() const {
        return size;
    }
    // 打印哈希表结构(调试用)
    void print() const {
        for (int i = 0; i < capacity; i++) {
            cout << "Bucket " << i << ": ";
            for (const Entry& entry : table[i]) {
                cout << "(" << entry.key << ":" << entry.value << ") ";
            }
            cout << endl;
        }
    }
};
// 测试代码
int main() {
    // 测试整数键哈希表
    HashTable<int, string> intHash;
    intHash.insert(1, "Apple");
    intHash.insert(2, "Banana");
    intHash.insert(31, "Cherry"); // 31 % 31 = 0,与1%31=1不冲突
    intHash.insert(32, "Date");   // 32 % 31 = 1,与1冲突(拉链处理)
    cout << "整数键哈希表:" << endl;
    intHash.print();
    // 输出:
    // Bucket 0: (31:Cherry) 
    // Bucket 1: (1:Apple) (32:Date) 
    // ...(其他桶为空)
    string val;
    if (intHash.find(32, val)) {
        cout << "找到32: " << val << endl; // 输出:找到32: Date
    }
    intHash.remove(2);
    cout << "删除键2后大小:" << intHash.getSize() << endl; // 输出:3
    // 测试字符串键哈希表
    HashTable<string, int> strHash;
    strHash.insert("Alice", 25);
    strHash.insert("Bob", 30);
    strHash.insert("Charlie", 35);
    cout << "\n字符串键哈希表:" << endl;
    strHash.print();
    return 0;
}

三、哈希表的关键特性

  • 负载因子(Load Factor)
    • 定义:负载因子 = 元素数量 / 表容量
    • 作用:衡量哈希表的拥挤程度,负载因子越大,冲突概率越高。
    • 策略:当负载因子超过阈值(通常0.7)时,触发扩容(容量翻倍),重新哈希所有元素。
  • 时间复杂度
    • 理想情况(无冲突):插入、删除、查找均为O(1)。
    • 最坏情况(所有元素冲突):退化为链表操作,O(n)。
    • 实际应用:通过合理设计哈希函数和扩容策略,平均复杂度接近O(1)。
  • 与其他数据结构对比
数据结构插入查找删除有序性适用场景
哈希表O(1)O(1)O(1)无序快速键值查询(如缓存、字典)
红黑树O(log n)O(log n)O(log n)有序需要范围查询(如std::map)
数组O(n)O(n)O(n)有序小数据量、随机访问

四、C++标准库中的哈希表

C++11引入了unordered_mapunordered_set,底层基于哈希表(链地址法)实现:

#include <unordered_map>
#include <unordered_set>
#include <iostream>
int main() {
    // unordered_map:键值对存储
    unordered_map<string, int> umap;
    umap["Apple"] = 5;
    umap["Banana"] = 3;
    // 查找
    if (umap.find("Apple") != umap.end()) {
        cout << "Apple: " << umap["Apple"] << endl;
    }
    // unordered_set:唯一元素存储
    unordered_set<int> uset = {1, 2, 3, 2}; // 自动去重
    for (int x : uset) {
        cout << x << " "; // 输出顺序不确定(无序)
    }
    return 0;
}

特点

  • 不保证元素顺序,迭代顺序可能随插入/删除变化。
  • 支持自定义哈希函数和相等性判断(通过模板参数)。
  • 性能优于map/set(红黑树),适合频繁查询的场景。

五、哈希表的应用场景

  1. 缓存系统:如浏览器缓存、数据库缓存,用哈希表快速查找缓存内容。
  2. 数据库索引:部分数据库用哈希索引加速等值查询(如MongoDB)。
  3. 去重操作:如unordered_set快速判断元素是否存在。
  4. 计数器:统计词频(如“给定字符串,找出出现次数最多的字符”)。
  5. 哈希映射:如URL短链接服务,将长URL映射为短码。

总结

哈希表通过哈希函数直接映射索引,结合冲突解决机制,实现了接近O(1)的操作效率,是平衡“时间-空间”的优秀选择。实际开发中,C++开发者通常直接使用unordered_map/unordered_set,但理解其底层原理(哈希函数、冲突处理、扩容机制)有助于更好地优化性能。哈希表的核心挑战在于哈希函数设计和冲突处理,合理的参数设置(如初始容量、负载因子)能显著提升其表现。

到此这篇关于C++经典的数据结构与算法之哈希表详解(Hash Table)的文章就介绍到这了,更多相关C++数据结构与算法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • FFmpeg中AVIOContext的使用方法详解

    FFmpeg中AVIOContext的使用方法详解

    AVIOContext是FFMPEG管理输入输出数据的结构体,这篇文章主要为大家详细介绍了这个结构体的具体使用,文中的示例代码讲解详细,需要的可以参考一下
    2023-08-08
  • 深入理解c++常成员函数和常对象

    深入理解c++常成员函数和常对象

    下面小编就为大家带来一篇深入理解c++常成员函数和常对象。小编觉得挺不错的,现在分享给大家,也给大家做个参考,一起跟随小编过来看看吧
    2016-05-05
  • 用C语言实现三子棋游戏

    用C语言实现三子棋游戏

    这篇文章主要为大家详细介绍了用C语言实现三子棋游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • OpenCV实现轮廓的发现

    OpenCV实现轮廓的发现

    这篇文章主要为大家详细介绍了OpenCV如何实现轮廓的发现,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-05-05
  • 搭建C语言开发环境(Windows平台)汇总

    搭建C语言开发环境(Windows平台)汇总

    本文给大家汇总了5种在WIN平台下搭建C语言开发环境的方法,包括一、在Windows平台配置GNU环境,二、使用Sublime Test开发C语言程序,三、使用VisualStudio开发C语言程序,四、搭建EclipseCDT集成开发环境,五、搭建Clion集成开发环境,有需要的小伙伴可以参考下
    2015-11-11
  • C语言详细讲解多维数组与多维指针

    C语言详细讲解多维数组与多维指针

    C 语言中的多维数组(multidimensional array)其实就是元素为数组的数组。多维指针根据声明的维数需要进行多次地址转换才能够取到目标数据。但指针作为数据变量,可以多次赋值,使其成为对数组操作访问的一大利器,所以指针和数组的结合才是重中之重
    2022-04-04
  • C++实现一个简单消息队列的示例详解

    C++实现一个简单消息队列的示例详解

    消息队列在多线程的场景有时会用到,尤其是线程通信跨线程调用的时候,就可以使用消息队列进行通信。本文将利用C++实现一个简单的消息队列,感兴趣的可以了解一下
    2022-12-12
  • 手拉手教你如何理解c/c++中的指针

    手拉手教你如何理解c/c++中的指针

    当你对C越来越熟悉时,你会发现把与指针搅和在一起的"类型"这个概念分成"指针的类型"和"指针所指向的类型"两个概念,是精通指针的关键点之一,这篇文章主要给大家介绍了关于c/c++中指针的相关资料,需要的朋友可以参考下
    2021-10-10
  • C语言数据的存储和取出详细讲解

    C语言数据的存储和取出详细讲解

    这篇文章主要介绍了C语言数据的存储和取出详细讲解,作者使用图文代码实例讲解,有感兴趣的同学可以学习研究下
    2021-02-02
  • C++实现大数相乘算法

    C++实现大数相乘算法

    这篇文章主要为大家详细介绍了C++实现大数相乘算法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-09-09

最新评论