hashmap赋值给另一个hashmap_图解设计一个 HashMap

本文通过分析LeetCode的「706. Design HashMap」问题,讲解如何设计一个不依赖内置哈希表库的HashMap。讨论了HashMap的核心思想——使用哈希函数将key转换为hashCode存储value,并探讨了哈希冲突及其解决方案,包括链地址法和开地址法。文中还给出了使用链地址法解决冲突的C++代码实现。
摘要由CSDN通过智能技术生成

目前我们学到的数据结构有:单链表,双向链表,栈,队列,循环队列,双端队列。今天学习 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,产生「哈希冲突」;

6dba0225ffe87ac9a0731150f246acc3.png

综上,设计一个哈希表需要做下面 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,被放到第一个位置,可以通过链表、红黑树进行存储。

e0e76fcd6a990b781e7c751cd17d4443.png

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

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

ad62206c8fb42fb396da48d765091c41.png

代码

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

d082e2b6df0e46ebbfc2666909cd183d.png

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

图解数据结构和算法

1fde3cb26870811dec1e49b043be737c.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值