C++实现哈希表。

本文介绍了C++中unordered_map、unordered_set与map、set的区别,并深入探讨哈希表,包括哈希函数、哈希冲突及其解决方法——闭散列和开散列,详细解析了两种方法的实现、负载因子以及它们在性能和空间使用上的考量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一. unordered_map unordered_set 和 map set的区别

二. 哈希

1. 哈希,哈希表,哈希函数。

2. 哈希冲突。

3. 哈希函数补充

3. 解决哈希冲突的两大方法:闭散列,开散列

闭散列

闭散列实现代码:

闭散列的删除问题:

负载因子:

开散列:

开散列哈希表代码实现:

开散列的负载因子和扩容问题:

开散列和闭散列的比较:


一. unordered_map unordered_set 和 map set的区别

1. map set底层采取的红黑树的结构,unordered_xxx 底层数据结构是哈希表。unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。

2. Java中对应的容器名为 HashMap HashSet TreeMap TreeSet,命名方面比C++好了很多。主要是早期C++并没有实现哈希结构的容器(C++11之前),也就是unordered系列,在C++11中新增了unordered_map,unordered_set,unordered_multimap,unordered_multiset,故因为历史命名问题,取了这样的名字。

3. 它们的使用大体上几乎一致。显著的差别是:
a、map和set为双向迭代器,unordered_xxx和是单向迭代器。
b、map和set存储为有序存储(红黑树结构,中序遍历有序),unordered_xxx为无序存储(哈希表的结构致使)

4. 性能差异:采取哈希表的unordered系列容器在大量数据的增删查改效率更优,尤其是查(搜索)

二. 哈希

1. 哈希,哈希表,哈希函数。

哈希的思想是:将元素的关键码与元素在哈希表中的存储位置构建某种函数关系,使得查找时,可以通过这个函数,直接获取元素的存储位置。对比顺序表和搜索树,顺序表需要将关键码一一对比,时间复杂度为O(N),而搜索树这样的结构,时间复杂度也是(log_2 N),取决于元素个数,树的高度,同样需要若干次比较。而哈希可以使得查找元素的存储位置不需要比较,直接通过函数获取。

将这个转换函数成为哈希函数(散列函数),得到的元素的存储位置成为哈希地址(也就是在哈希表中的下标,哈希表一般为顺序表结构),将构造出的数据结构成为哈希表(散列表)。

2. 哈希冲突。

难免有某些元素的不同关键码通过同一个哈希函数转换后得到的哈希地址相同,这种现象称为哈希冲突或哈希碰撞。

故,要想构造出理想的哈希存储结构,必须解决哈希冲突,合理安排那些关键码不同,而哈希地址相同的元素。

3. 哈希函数补充

哈希函数有很多种,也就是将元素的关键码,转换为元素存储位置的函数。哈希函数的设计越精妙,产生哈希冲突的概率越低,但是无法完全避免哈希冲突。

下方哈希表的实现代码中,采取的哈希函数为除留余数法,即将关键码取模哈希表的长度,获取哈希地址。

3. 解决哈希冲突的两大方法:闭散列,开散列

闭散列

闭散列:也叫开放定址法。核心思想比较简单:若某两个元素发生哈希冲突,第二个得到该哈希地址的元素插入时,将该元素存放到哈希表中发生哈希冲突的“下一个”空位置中去。其实也就是再找一个空位置,然后插入。

如何寻找下一个“空位置”的方法有两种,1. 线性探测,即从哈希地址处起,逐个位置进行判断,直到找到空位置。2. 二次探测:第一次看hashAdd+0^2下标处有没有被占用(设哈希地址为hashAdd。其实也就是看获取的哈希地址处有没有没被占用),第二次看hashAdd+1^2,第三次看hashAdd+2^2,直到找到没有被占用的位置。

相比于线性探测,二次探测只是将发生哈希冲突的元素的存储位置分离一些,不像线性探测一样连续存储,能一定程度减少哈希冲突带来的性能损耗,但是不能根本解决问题。

闭散列实现代码:

// 开散列(链地址法)解决哈希表中的哈希冲突
namespace OpenHashing
{
    // 这里仅仅是实现哈希表的最简单逻辑,Find等函数的实现没有考虑unordered_map等容器
    template <class K, class V>
    struct HashNode
    {
        HashNode<K,V>(const pair<K,V>& kv)
        :_kv(kv)
        { }
        pair<K, V> _kv;
        HashNode* _next = nullptr;
    };

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

    // 模板特化
    template <>
    struct HashAddressConvert<string>
    {
        size_t operator()(const string& str) {
            size_t sum = 0;
            for(auto&ch:str)
            {
                sum*=131;
                sum+=ch;
            }
            return sum;
        }
    };

    template <class K, class V, class HDC = HashAddressConvert<K>>
    class HashTable
    {
        typedef HashNode<K, V> Node;
    public:
        ~HashTable() {
            for(auto& ptr : _table) {
                Node* cur = ptr;
                while(cur) {
                    Node* next = cur->_next;
                    delete cur;
                    cur = next;
                }
                ptr = nullptr;
            }
        }
        bool Insert(const pair<K, V>& kv) {
            if(Find(kv.first)) {
### C++ 实现哈希表的方法及代码示例 在 C++ 中,可以通过多种方式实现哈希表。一种简单且直接的方式是使用标准库中的 `std::unordered_map`[^1]。以下是一个基于 `std::unordered_map` 的实现示例: ```cpp #include <iostream> #include <unordered_map> int main() { // 创建一个 unordered_map,用于存储学生的分数 std::unordered_map<std::string, int> scores; // 插入键值对 scores["Alice"] = 95; scores["Bob"] = 85; scores["Charlie"] = 90; // 访问和修改值 std::cout << "Bob's score: " << scores["Bob"] << std::endl; scores["Bob"] = 88; std::cout << "Updated Bob's score: " << scores["Bob"] << std::endl; // 检查键是否存在 if (scores.find("Alice") != scores.end()) { std::cout << "Alice's score exists" << std::endl; } // 遍历哈希表 for (const auto& pair : scores) { std::cout << pair.first << ": " << pair.second << std::endl; } return 0; } ``` 此外,也可以通过手动实现哈希表来更深入地理解其工作原理。以下是一个简单的手写哈希表实现[^2],采用开放地址法解决冲突: ```cpp #include <iostream> #include <cstring> using namespace std; const int N = 200003; // 哈希表大小,通常是数据量的2~3倍 const int null = 0x3f3f3f3f; // 表示空位置 int h[N]; // 哈希表 // 初始化哈希表 void init() { memset(h, 0x3f, sizeof h); } // 查找函数,同时用于插入和查找 int find(int x) { int t = (x % N + N) % N; // 计算哈希值 while (h[t] != null && h[t] != x) { // 如果当前位置已占用且不等于目标值 t++; // 线性探测 if (t == N) t = 0; // 如果遍历到末尾,则回到开头 } return t; // 返回目标值的位置或可插入位置 } int main() { init(); // 初始化哈希表 // 插入元素 int x = 10; int idx = find(x); if (h[idx] == null) { // 如果位置为空,则插入 h[idx] = x; cout << "Inserted " << x << " at index " << idx << endl; } else { cout << x << " already exists in the hash table" << endl; } // 查找元素 x = 10; idx = find(x); if (h[idx] != null) { cout << "Found " << x << " at index " << idx << endl; } else { cout << x << " not found in the hash table" << endl; } return 0; } ``` 以上两种方法分别展示了如何利用标准库和手动实现哈希表。前者更简洁高效,后者则有助于理解哈希表的底层机制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值