• 从零带你底层实现unordered_map (1)


    💯 博客内容:从零带你实现unordered_map

    😀 作  者:陈大大陈

    🚀 个人简介:一个正在努力学技术的准C++后端工程师,专注基础和实战分享 ,欢迎私信!

    💖 欢迎大家:这里是CSDN,我总结知识和写笔记的地方,喜欢的话请三连,有问题请私信 😘 😘 😘

    目录

    超级容易踩坑的地方

    unordered_map怎么实现

    哈希冲突

    开放寻址法

    代码


     

     unordered_map也就是哈希表,今天就来讲解它的用法。

    unordered的意思是“无序”,这里强调了和map功能上的不同,因为map里面的东西是排好序的。

    超级容易踩坑的地方

    它是一个单向的迭代器。

    为什么专门提到这个呢?因为这是我踩过坑的地方!!

    单向迭代器压根就不能使用sort函数来排序!

    std::unordered_map的迭代器类型是ForwardIterator,而不是sort函数要求的RandomAccessIterator,这里不符合。

    我们要排序的话,还是将unordered_map里存的值,转存到vector里面。

    然后我们再自定义一个排序方法,对vector进行排序。

    可参考下面的代码:

    1. class Solution {
    2. public:
    3. struct comp
    4. {
    5. bool operator()(const pairint>&p1,const pairint>&p2)
    6. {
    7. return p1.second>p2.second||(p1.second==p2.second&&p1.first
    8. }
    9. };
    10. vector topKFrequent(vector& words, int k) {
    11. unordered_mapint> hash;
    12. for(auto &str:words) hash[str]++;
    13. vectorint>> sortV(hash.begin(),hash.end());
    14. sort(sortV.begin(),sortV.end(),comp());
    15. vector v;
    16. for(int i=0;i
    17. {
    18. v.push_back(sortV[i].first);
    19. }
    20. return v;
    21. }
    22. };

    692. 前K个高频单词 - 力扣(LeetCode) 

    也可以使用std::set结构对键进行排序,如下所示:

    1. std::unordered_map<int, int> unordered;
    2. std::set<int> keys;
    3. for (auto& it : unordered) keys.insert(it.first);
    4. for (auto& it : keys) {
    5. std::cout << unordered[it] << ' ';
    6. }

    unordered_map怎么实现

    哈希冲突

    hash也叫散列。

    举一个例子,学校图书馆提供借书义务,怎么快速找到某个同学借的书?

    我们可以引入一个关键值(日期),借书记录存的位置。

    哈希和散列就是这样。

    关键值和存储位置,建立一个关联关系。

    如果值的跳跃很大,那空间就会很浪费。

    有一个方法可以减少空间浪费,就是让数值统一对一个数取模。

    但是这样就又会衍生出一个问题,就是哈希碰撞,也叫做哈希冲突。

    例如,3对10取模是3,33对10取模也是3

    这样一来,本来不同位置的两个值,现在映射到了相同的位置。

    对于闭散列,我们有一个方法来解决这种情况。

    开放寻址法

    当前空间已经被占用,在开放空间里按照某种规则,再寻找一个未被占用的位置存储。

    开放寻址法有两种方法。

    1.线性探测  hashi+i (i>=0)

    2.二次探测  hashi+i^2 (i>=0)

    不需要担心后面找不到位置,因为有负载因子在控制。

    负载因子是当前值的个数和空间的比率,它会保持在一个值一下。

    到一定程度,就会引发扩容。

    负载因子太大,冲突可能会增加,效率降低。

    负载因子太小,冲突会变少,但是空间消耗会增大,空间利用率降低。

    要底层实现哈希表,有一个很尴尬的问题。

    我们不知道如何判断一个位置有没有存值。

    因为find是碰到空就停止,假设我们删除了20,那20的位置变为空。

    我们再想寻找21,22,就找不到了,因为find在20的位置就停止了。

    所以,我们需要区分开两种情况,一个位置是被删除了而导致空,还是本来就是空。

    假设是本来就是空,那我们到这个位置就可以停止查找,假设是被删除才导致的空,我们就继续查找下去。

    知道查找到这个值,或者查找到空为止。

    不能直接扩容,因为映射关系会改变。

    要扩容的话,要直接新开一段空间,重新映射,再释放旧空间。

    代价很大,但是没有别的方式。

    最难想到的就是扩容,咱们就新开一段空间,复用一下插入函数。

    最后用swap交换一下新旧空间的内容。

    这样写的好处是,函数调用完成后会自动释放空间。

    下面是第一版的代码,之后的补全版本代码会在接下来几个博客中发出来。

    代码

    1. #pragma once
    2. #include<vector>
    3. namespace bit
    4. {
    5. enum Status
    6. {
    7. EMPTY,
    8. EXIST,
    9. DELETE
    10. };
    11. template<class T, class V>
    12. struct HashData
    13. {
    14. pair<K, V> _kv;
    15. Status _s;//状态
    16. };
    17. template<class T,class V>
    18. class HashTable
    19. {
    20. public:
    21. HashTable()
    22. {
    23. _tables.resize(10);
    24. }
    25. bool insert(const pair<K, V>& kv)
    26. {
    27. if (_n*10 / _tables.size() == 0.7) //因为整形相除不可能是0.7,所以乘10,也可以转换成double
    28. {
    29. size_t NewSize = _tables.size() * 2;
    30. HashTable<K, V> newHT;
    31. newHT._tables.resize(NewSize);
    32. for (int i = 0; i < _tables.size(); i++)
    33. {
    34. if (_tables[i]._s == EXIST)
    35. {
    36. newHT.insert(_tables[i].kv);
    37. }
    38. }
    39. _tables.swap(newHT._tables);
    40. }
    41. size_t hashi = kv.first % _tables.size();
    42. while (_tables[hashi]._s == EXIST)
    43. {
    44. ++hashi;//当等于存在时,往后查找
    45. hashi %= _tables.size();//防止越界访问
    46. }
    47. _tables[hashi]._kv = kv;
    48. _tables[hashi]._s = EXIST;
    49. ++_n;
    50. return true;
    51. }
    52. private:
    53. vector<HashData> _tables;
    54. size_t _n = 0;//存储的关键字的个数
    55. };
    56. }

  • 相关阅读:
    从零开始 Spring Boot 25:MyBatis II
    绿色工厂申报流程
    如何建立云存储应急演练体系及进行场景设计
    maven下载及安装
    原码、反码、补码
    基于模糊控制算法的快速反射镜 系统扰动抑制
    2023跨境秋季大促备战攻略
    Excel中输入整数却总是显示小数,如何调整?
    C++设计模式之代理模式(结构型模式)
    windows下python opencv ffmpeg读取摄像头实现rtsp推流 拉流
  • 原文地址:https://blog.csdn.net/weixin_73534885/article/details/134515705