目前我们学到的数据结构有:单链表,双向链表,栈,队列,循环队列,双端队列。今天学习 LeetCode 的 「 706. Design HashMap 」,从设计一个 HashMap 到掌握其内部原理。题目要求:

Design a HashMap without using any built-in hash table libraries.
To be specific, your design should include these functions:put(key, value) : Insert a (key, value) pair into the HashMap. If the value already exists in the HashMap, update the value.get(key): Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key.remove(key) : Remove the mapping for the value key if this map contains the mapping for the key.

All keys and values will be in the range of [0, 1000000].

The number of operations will be in the range of [1, 10000].

题目要求设计一个 HashMap,不能使用语言提供的类似哈希表的库,比如HashMap,dict,map,需要实现下面几个方法:

1. put(key, value) : 根据 key 插入一个 value,如果 key 已经存在,更新 value;

2. get(key): 根据一个 key 获取对应的值,如果未找到对应的 value,返回 -1;

3. remove(key) : 根据 key 来删除对应的值。

分析

HashMap 是一种典型的以空间换取时间的数据结构,在设计缓存算法 LRU 和 LFU 当中用到了 C++ 提供的 unorder_map,利用HashMap 的特点,做到存取时间复杂度为 O(1) 。今天我们要掌握如何设计一个 HashMap。设计之前需要知道 HashMap 是一种什么样的数据结构?

HashMap 的核心思想是 「 把 key 通过一种方式转换成一个 hashCode(一个整形数),通过 hashCode 来存取对应的 value 」,转换的方式就是哈希函数,在转换的过程中,不同的 key 可能会生成同一 hashCode,这将产生「哈希冲突」。

一图胜千言!

插入 key 对应的 value 函数为:put(key, value):

执行 put(1, 1),1 的 hash 值 hash(1) = 1 % 5 = 1,放到 1 个位置;

执行 put(4, 4),4 的 hash 值 hash(4) = 4 % 5 = 4,放到 4 个位置;

执行 put(6, 6),6 的 hash 值 hash(6) = 6 % 5 = 1,放到 1 个位置,第一个位置已经存放了 1,产生「哈希冲突」;

综上,设计一个哈希表需要做下面 2 件事:

1.设计哈希函数;

衡量一个哈希函数设计的好坏是看它是否能够让 value 「均匀分布」,也就是产生哈希冲突越少越好。语言本身一般会提供计算 hashCode 的方法,比如 OC 中的 NSObject 类提供了 hash 方法:

NSString *name = @"Lefe_x";NSString *des = @"超越技术公众号做图解算法";NSLog(@"hash(name) = %@, hash(des)=%@", @(name.hash), @(des.hash));// hash(name) = 7306077673678745, hash(des)=7723704617483326955

2.解决哈希冲突;

不同的 key 生成的 hashCode 相同就产生了哈希冲突,解决冲突有主要有下面几种方式:

链地址法:产生哈希冲突后,把产生冲突的元素使用某种方式「组合」到一起,可以使用链表、红黑树,或者使用其它数据结构。

把 1、6、3、4、13 分别 put 到哈希表中,1、6、13 的哈希值均为 1,被放到第一个位置,可以通过链表、红黑树进行存储。

开地址法:产生哈希冲突后,把 value 放到其它空闲位置,可以使用线性探测法放到下一个空闲位置;使用平方探测法,放到第1个、第 4个、第9个、第16个......空闲位置;使用二次哈希,通过另外一个哈希函数再计算一次哈希值。

使用线性探测法解决冲突,把 1、6、3、4、13 分别 put 到哈希表中,1、6、13 的哈希值均为 1,会产生冲突,当遇到冲突后,把 value 插入到下一个位置。保存结果如下图:

代码

通过上面的分析可知,实现一个 HashMap,需要一个实现一个哈希函数和解决哈希冲突,代码中通过「链地址法」来解决哈希冲突。题目中的 key 和 value 的取值范围为 [ 0 - 1000000 ]。代码原理如图:

C++ 代码如下(来源于 LeetCode):

#include #include #include using namespace std;class MyHashMap {    size_t m_size = 10000;    vector<listint, public:    // 初始化,设置大小    MyHashMap() {        m_data.resize(m_size);    }    // 哈希函数    int hashCode(int key) {        return key % m_size;    }        // 根据 key 存储对应的 value,如果 key 已经存在,更新 value    void put(int key, int value) {        // 根据哈希函数找到对应的链表        auto &list = m_data[hashCode(key)];        for (auto & val : list) {            // 如果已经存在,根据 key 来更新对应的值            if (val.first == key) {                val.second = value;                return;            }        }        // 插入链表的尾部        list.emplace_back(key, value);    }        // 根据 key 来获取值    int get(int key) {        const auto &list = m_data[hashCode(key)];        if (list.empty()) {            return -1;        }        for (auto & val : list) {            // 如果已经存在,找到了对应的值            if (val.first == key) {                return val.second;            }        }        return -1;    }        // 根据 key 删除对应的值    void remove(int key) {        auto &list = m_data[hashCode(key)];        // 找到节点后删除        list.remove_if([key](auto n) {            return n.first == key;        });    }};

总结

至此,一个简单的 HashMap 就完成了,如果设计一个复杂的 HashMap,需要考虑数据达到一定程度后,需要对 vector 进行扩容、缩容处理,如果冲突达到某一个量级后,需要考虑更换 list 这个数据结构,比如换成红黑树。

推荐阅读:

论证:学习数据结构和算法很重要

图解 LFU cache

图解数据结构和算法

hashmap赋值给另一个hashmap_图解设计一个 HashMap相关推荐

  1. HashMap底层红黑树实现(自己实现一个简单的红黑树)

    文章整理于小刘老师讲源码 视频学习链接:小刘老师讲解红黑树 JDK集合源码之HashMap解析(上) JDK集合源码之HashMap解析(下) 1.树结构入门 1.1 什么是树? 树(tree)是一种 ...

  2. python输入一个正整数n求下列算式的值_C语言编写程序:输入一个正整数x和一个正整数n,求下列算式的值。,C语言 编写一个程序,输入一个正整数,求出它是几位数。...

    导航:网站首页 > C语言编写程序:输入一个正整数x和一个正整数n,求下列算式的值.,C语言 编写一个程序,输入一个正整数,求出它是几位数. C语言编写程序:输入一个正整数x和一个正整数n,求下 ...

  3. Java黑皮书课后题第4章:*4.17(一个月的天数)编写一个程序,提示用户输入一个年份和一个月份名称的前3个字母(第一个字母使用大写形式),显示该月中的天数。如果月份非法则显示出错信息

    *4.17(一个月的天数)编写一个程序,提示用户输入一个年份和一个月份名称的前3个字母(第一个字母使用大写形式),显示该月中的天数.如果月份非法则显示出错信息 题目 题目概述 破题 运行示例 代码 题 ...

  4. 在一个由 'L' , 'R' 和 'X' 三个字符组成的字符串(例如RXXLRXRXL)中进行移动操作。一次移动操作指用一个LX替换一个XL,或者用一个XR替换一个RX。现给定起始...

    在一个由 'L' , 'R' 和 'X' 三个字符组成的字符串(例如"RXXLRXRXL")中进行移动操作.一次移动操作指用一个"LX"替换一个"XL ...

  5. java 一个数组key一个数组value_在各种语言中,使用key在map中获取value 和 使用下标获取数组中的数据 相比哪个更快?...

    数组和集合的效率问题数组是JAVA语言内置的数据类型,它是一个线性的序列,所以它可以快速的访问其他的元素.但是速度是要有代价的,当你创建了一个数组之后,它的容量就固定了,而且在其生命周期里是不能改变的 ...

  6. DOM算法系列002-寻找指定DOM节点的上一个或下一个节点

    DOM操作算法002-寻找指定DOM节点的上一个或下一个节点-- getDomNode 当我们需要寻找指定DOM节点的上一个节点或下一个节点时,我们可能第一时间会想到下面两个API: node.pre ...

  7. 【Jquery】-------JS实现关键字检索html内容,符合关键字的匹配项,进行标注背景色,可进行上一个,下一个切换定位

    JS实现关键字检索html内容,符合关键字的匹配项,进行标注背景色 核心代码 全部代码 展示效果 核心代码 全部代码 这个代码主要功能: 通过关键字检索出html内容匹配项 可进行上一个,下一个切换定 ...

  8. C语言中的内聚与耦合(遵循“一个函数,一个功能”的原则)

    文章目录 一.原理篇 低耦合 非直接耦合与数据耦合(值传递不传指针) 特征耦合(传指针并可修改指针指向内存:不同函数打开同一文件进行操作) 外部耦合(访问同一全局变量,不通过参数表传递全局变量信息:通 ...

  9. C语言返回指针的函数,指针函数,让一个函数返回一个字符串

    C语言函数返回指针的函数(指针函数) 什么是返回指针的函数 一个函数可以返回一个整形值 字符型值 实型值 1.如果一个函数它的返回值是一个地址(是一个指针的话),这个函数是一个返回值是指针即指针函数. ...

最新文章

  1. dbscan算法中 参数的意义_基于变参数的DBSCAN算法
  2. 【pandas学习笔记】综合整理
  3. 怎么自学python自动化测试-Python移动自动化测试面试 学习 教程
  4. C语言遥控器程序,红外遥控
  5. HAAR、LBP分类器训练
  6. nx600打印机打印设置_win7打印机共享怎么设置
  7. PHP优于Node.js的五大理由
  8. leetcode 无重复字符的最长子串
  9. anaconda下载jupyter写python_如何安装Anaconda3和使用Jupyter
  10. 【连载】如何掌握openGauss数据库核心技术?秘诀三:拿捏存储技术(3)
  11. Python Re 模块超全解读
  12. android中正则表达式截取html中的video标签
  13. Only the Paranoid Survive
  14. RxSwift极简入门
  15. 区块链主流开源技术体系介绍(转)
  16. 芯片模型算力指标TOPS FLOPS MAC MACC MADD关系
  17. php中文输出有乱码怎么办,php中文输出乱码怎么办
  18. 游戏直播平台新赛程:负重前行与危中求生
  19. 2021 年 Q4 随笔
  20. buuctf在线测评web Secret File

热门文章

  1. 2020-11-9(intent显式意图和隐式意图)
  2. Linux中如何使用Htop监控工具?【网络安全】
  3. 【渗透测试】一次从黑盒转向白盒
  4. 世安杯CTF writeup
  5. 7、CSS 属性选择器
  6. 2015年蓝桥杯省赛题解
  7. 【C / C++ 】memset函数
  8. Thymeleaf抽取公共页面片段
  9. openwrt 遍译php_[OpenWrt Wiki] OpenWrt编译 – 说明
  10. 【Java】6.6 Java 8 改进的结构